BanditHijo.dev

Mengenal Single Table Inheritance dengan Devise pada Rails (Contoh 2)

Created at: 2020-02-22
Author by: BanditHijo

Prerequisite

ruby 2.6.3 rails 5.2.4 postgresql 11.5

Prakata

Apa itu Single Table Inheritance?

Dapat didefinisikan sebagai tabel induk yang mewariskan sifat-sifatnya pada tabel anakan yang berelasi dengannya.

Ahahaha (^_^) definisi macam apa itu.

Abaikan.

Pada saat mengimplementasikan Single Table Inheritance (STI), saya menemukan lebih dari satu cara pada Rails. Maka dari itu, tulisan ini akan saya bagi dalam beberapa contoh.

Catatan kali ini adalah contoh kedua.

Teman-teman dapat melihat contoh pertama di sini.

Kira-kira seperti ini ERD-nya.

Gambar 1

Gambar 1. ERD Single Table Inheritance antara user model dengan participants dan sponsors model

Dari diagram di atas, saya hanya membuat satu buah tabel, yaitu users.

Tabel users ini hasil generate dari Devise.

Yak! Pada catatan kali ini, saya akan mencatat bagaimana membuat Single Table Inheritance (STI) dengan Devise.

Sekenario

Saya berencana membuat sebuah website untuk registrasi sebuah event.

Dimana user yang mendaftar akan langsung diarahkan untuk mendaftar sebagai participant atau menjadi sponsor.

Menurut saya, ini adalah contoh yang pas untuk memnggunakan STI.

Dimana model user akan menurunkan sifatnya pada model participant dan sponsor.

Pemecahan Masalah

Saya akan memulai dari pemasangan Devise

Migrations

Saya akan membuat tabel users dengan menggunakan generator yang dimiliki oleh Devise.

$ rails g devise user

Generator di atas juga akan sekalian membuatkan kita model user.

db/migrations/20200221021307_devise_create_users.rb
1class DeviseCreateUsers < ActiveRecord::Migration[5.2]
2 def change
3 create_table :users do |t|
4 ## Database authenticatable
5 t.string :email, null: false, default: ""
6 t.string :encrypted_password, null: false, default: ""
7
8 # ...
9 # ...
10
11 t.timestamps null: false
12 end
13
14 # ...
15 # ...
16 end
17end

Nah, sebenarnya bisa saya modifikasi migration ini, dengan menambahkan field :name dan :type.

Namun, saya memilih untuk membuat migration untuk memanbahkan field name dan type secara satu persatu.

Tidak ada alasan, hanya untuk berlatih saja membuat migration.

$ rails g migration add_name_and_name_to_users

Kemudian, tambahkan manual add_column untuk :name dan :type.

db/migrations/20200221021551_add_name_type_to_users
1class AddNameAndTypeToUsers < ActiveRecord::Migration[5.2]
2 def change
3 add_column :users, :name, :string
4 add_column :users, :type, :string
5 end
6end

Setelah itu, jalankan semua migration di atas.

$ rails db:migrate

Models

Kemudian buat dua model untuk participant dan sponsor.

Kita dapat memanfaatkan rails generator untuk membuat kedua model (participant dan sponsor) dengan turunan dari model user.

$ rails g model participant --parent=User
$ rails g model sponsor --parent=User

Karena saya sudah menambahkan field :type pada tabel users, maka kedua model ini yang akan mendapat turunan sifat dari model user dengan mengambil dari nama class dari masing-masing model.

Kalau kita buka, kedua model tersebut, class dari kedua model tersebut sudah merupakan turunan dari model user.

app/models/participant.rb
1class Participant < User
2end
app/models/sponsor.rb
1class Sponsor < User
2end

Routes

Kemudian, pada routes untuk :users juga ganti menjadi dua buah untuk masing-masing model.

!filename: config/routes.rb
Rails.application.routes.draw do
  ...
  devise_for :participants
  devise_for :sponsors
end

Views & Controller

Untuk views template, saya perlu membuat homepage dan halaman dashboard ketika user sudah melakukan sign up dan sign in.

Halaman authentikasi seperti sign up dan sign in, bisa di generate dari Devise.

Oke, langsung saya mulai dari membuat Homepage.

Saya akan mulai dengan mengenerate page controller dengan action index.

$ rails g controller Pages index
app/controllers/pages_controller.rb
1class PagesController < ApplicationController
2 def index; end
3end

Kemudian, pada routes definisikan pages menjadi root.

Letakkan di paling atas.

config/routes.rb
1Rails.application.routes.draw do
2 root to: 'pages#index'
3
4 devise_for :participants
5 devise_for :sponsors
6end

Selanjutnya buat dashboards controller untuk tempat participant dan sponsor mendarat setelah sign up atau sign in.

$ rails g controller dashboard/dashboard index
app/controllers/dashboard/dashboard_controller.rb
1class Dashboard::DashboardController < ApplicationController
2 before_action :authenticate_user!
3
4 def index; end
5end

Nah, saya akan mendifinisikan apabila user mengakses halaman /dashboard, maka, user akan langsung diarahkan sebagai participant terlebih dahulu.

app/controllers/application_controller.rb
1class ApplicationController < ActionController::Base
2 devise_group :user, contains: [:participant, :sponsor]
3end

Urutan di dalam array menjadi penentu, scope mana yang akan diprioritaskan apabila user mengakses halaman /dashboard apabaila belum melakukan sing up atau sign in.

Kemudian atur routenya.

config/routes.rb
1Rails.application.routes.draw do
2 root to: 'pages#index'
3
4 devise_for :participants
5 devise_for :sponsors
6
7 namespace :dashboard do
8 root to: 'dashboard#index'
9 end
10end

Selanjutnya untuk view dari Devise.

Nah, ada banyak cara yang dapat dilakukan. Mulai dari mengenerate view untuk masing-masing scope, atau mengcopy paste saja view template hasil view generate.

Saya memilih untuk mengenerate kedua scope.

$ rails g devise:views participant
$ rails g devise:views sponsor

Lalu mengedit bagian registrations new dan edit untuk menambahkan field :name.

app/views/participants/registrations/new.html.erb
1<h2>Participant Sign up</h2>
2
3<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
4 <%= render "devise/shared/error_messages", resource: resource %>
5
6 <div class="field">
7 <%= f.label :name %><br />
8 <%= f.text_field :name, autofocus: true %>
9 </div>
10
11 ...
12 ...
13
14<% end %>
15
16<%= render "devise/shared/links" %>
app/views/sponsors/registrations/new.html.erb
1<h2>Sponsors Sign up</h2>
2
3<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
4 <%= render "devise/shared/error_messages", resource: resource %>
5
6 <div class="field">
7 <%= f.label :name %><br />
8 <%= f.text_field :name, autofocus: true %>
9 </div>
10
11 ...
12 ...
13
14<% end %>
15
16<%= render "devise/shared/links" %>

Selanjutnya, tinggal memberikan ijin (permit) kepada :name saat melakukan sign up.

Saya akan tambahkan saja pada application controller.

app/controllers/application_controller.rb
1class ApplicationController < ActionController::Base
2 devise_group :user, contains: [:participant, :sponsor]
3 before_action :configure_permitted_parameters, if: :devise_controller?
4
5 private
6
7 # If you have extra params to permit, append them to the sanitizer.
8 def configure_permitted_parameters
9 added_attrs = [:email, :password, :password_confirmation, :remember_me, :name]
10 devise_parameter_sanitizer.permit :sign_up, keys: added_attrs
11 devise_parameter_sanitizer.permit :account_update, keys: added_attrs
12 end
13
14 # The path used after sign up.
15 def after_sign_up_path_for(resource_or_scope)
16 resource_or_scope.is_a?(User) ? dashboard_root_path : root_path
17 end
18
19 # The path used after sign in.
20 def after_sign_in_path_for(resource_or_scope)
21 resource_or_scope.is_a?(User) ? dashboard_root_path : root_path
22 end
23end

Saya juga menambahkan redirect path apabila user sudah melakukan sign up atau sign in untuk diarahkan ke halaman dashboard.

Nah selesai!

Sepertinya segini saja.

Untuk contoh proyeknya dapat dilihat di repo github di sini.

Mudah-mudahan catatan yang sederhana ini dapat bermanfaat bagi teman-teman yang memerlukan.

Terima kasih.

(^_^)

Tambahan

  1. Membuat Relasi dengan Hanya Salah Satu Type pada Single Table Inheritance Model di Rails

Referensi

  1. vsmedia.co.uk/single-table-inheritance-sti-devise/
    Diakses tanggal: 2020/02/21

  2. guides.rubyonrails.org/routing.html#controller-namespaces-and-routing
    Diakses tanggal: 2020/02/21

  3. api.rubyonrails.org/classes/ActiveRecord/Inheritance.html
    Diakses tanggal: 2020/02/21