BanditHijo.dev

Membuat User dan Admin Terpisah pada Rails yang menggunakan Devise

Created at: 2019-12-15
Author by: BanditHijo

Prerequisite

Ruby 2.6.3 Rails 5.2.4 PostgreSQL 11.5

Prakata

Kali ini saya ingin mencatat mengenai web aplikasi yang memiliki tampilan frontend terpisah antara user dan admin. Tentu saja dengan menggunakan Rails.

Contoh mudahnya seperti aplikasi blog, Wordpress atau Blogspot.

Kedua aplikasi ini disebut CMS (Content Management System). Di mana web aplikasi ini mempunyai dua buah tampilan yang berbeda antara tampilan untuk pengunjung dan tampilan untuk author (penulis) atau admin.

Nah, kegunaan pemisahan User dengan Admin pada catatan kali ini, nantinya dapat dimanfaatkan untuk membuat web aplikasi seperti CMS.

Eksekusi

Kali ini saya sedikit rajin.

Saya akan mencatat prosesnya dari awal project dibuat. Hehehe.

Inisiasi Project

Saya akan membuat project baru menggunakan Rails 5.2.4 dengan PostgreSQL sebagai database engine.

$ rails _5.2.4_ new blog_spot -d postgresql

Kalau proses pembuatan sudah selesai, masuk ke dalam project.

$ cd blog_spot

Periksa spesifikasi versi Rails dan Ruby.

$ ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
$ rails -v
Rails 5.2.4

Selanjutnya create database dengan perintah berikut ini.

$ rails db:create
Created database 'blog_spot_development'
Created database 'blog_spot_test'

Lalu, jalankan Rails server untuk sekedar melihat apakah project berhasil dijalankan atau tidak.

$ rails s

gambar_1

Gambar 1 - Default Welcome Page pada Rails Project

Yay! Berhasil.

Entah mengapa saya suka melihat Default Rails Welcome Page ini. Dari sedikit web framework yang sudah saya coba seperti Codeigniter, Laravel, Django dan React. Rails memiliki tampilan Default Welcome Page yang menurut saya paling menarik.

Devise Gem

Devise adalah gem yang akan saya gunakan untuk menghandle authentication system.

Pasang pada Gemfile.

FILEGemfile
1
1
22
33
4
# ...
5# ...
6gem 'devise', '~> 4.7', '>= 4.7.1'
7

Install Devise gem yang baru saja kita pasang.

$ bundle install

Jalankan generator yang disediakan oleh Devise untuk menginisiasi file config yang disediakan oleh Devise.

$ rails generate devise:install
Running via Spring preloader in process 349251
     create  config/initializers/devise.rb
     create  config/locales/devise.en.yml
===============================================================================

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

===============================================================================

Hasil generate tersebut akan menghasilkan dua buah file yang dapat kita lihat pada output di atas.

Selanjutnya saya membuat model user dan admin dengan memanfaatkan generator yang disediakan oleh Devise.

Saya akan membuat untuk model admin terlebih dahulu.

$ rails g devise admin
Running via Spring preloader in process 368446
      invoke  active_record
      create    db/migrate/20191216044109_devise_create_admins.rb
      create    app/models/admin.rb
      invoke    test_unit
      create      test/models/admin_test.rb
      create      test/fixtures/admins.yml
      insert    app/models/admin.rb
       route  devise_for :admins

Kemudian untuk model user.

$ rails g devise user
Running via Spring preloader in process 368446
      invoke  active_record
      create    db/migrate/20191216044641_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

Lalu jalankan migration-nya.

$ rails db:migrate
== 20191216044109 DeviseCreateAdmins: migrating ===============================
-- create_table(:admins)
   -> 0.0167s
-- add_index(:admins, :email, {:unique=>true})
   -> 0.0134s
-- add_index(:admins, :reset_password_token, {:unique=>true})
   -> 0.0056s
== 20191216044109 DeviseCreateAdmins: migrated (0.0360s) ======================

== 20191216044641 DeviseCreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0102s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0077s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0064s
== 20191216044641 DeviseCreateUsers: migrated (0.0246s) =======================

Cek status dengan.

$ rails db:migrate:status
database: blog_spot_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20191216044109  Devise create admins
   up     20191216044641  Devise create users

Oke, migration untuk user dan model telah berhasil dimigrasikan ke skema database.

Dengan begini, sekarang saya sudah memiliki beberapa fitur yang disediakan oleh Devise, seperti:

  1. Authentikasi
  2. Registrasi
  3. Edit
  4. dll.

Devise juga mengenerate route untuk model admin dan user.

FILEconfig/routes.rb
1
1
22
33
44
5
Rails.application.routes.draw do
6 devise_for :admins
7 devise_for :users
8end
9

Cek route yang tersedia pada Browser.

http://localhost:3000/rails/info/routes

Selanjutnya, saya akan mulai dari controller.

Controller

Saya akan menggunakan Controller Namespaces and Routing.2. Untuk memisahkan antara admin dan user dengan struktur direktori seperti ini.

├─ app/
│  ├─ assets/
│  ├─ channels/
│  ├─ controllers/
│  │  ├─ admins/
│  │  │  └─ dashboard_controller.rb
│  │  ├─ concerns/
│  │  ├─ public/
│  │  │  ├─ about_controller.rb
│  │  │  ├─ contact_controller.rb
│  │  │  └─ homepage_controller.rb
│  │  ├─ users/
│  │  │  └─ dashboard_controller.rb
│  │  ├─ admins_controller.rb
│  │  ├─ application_controller.rb
│  │  └─ users_controller.rb
│  ├─ ...
│  ...
├─ ...
...

Kemudian isi dari file-file controller tersebut akan seperti ini.

Untuk Controller Namespaces pada Admins.

FILEapp/controllers/admins_controller.rb
1
1
22
33
4
class AdminsController < ApplicationController
5 layout :admins
6end
7
FILEapp/controllers/admins/dashboard_controller.rb
1
1
22
33
4
class Admins::DashboardController < AdminsController
5 def index; end
6end
7

Untuk Controller Namespaces pada Users.

FILEapp/controllers/users_controller.rb
1
1
22
3
class UsersController < ApplicationController
4end
5
FILEapp/controllers/users/dashboard_controller.rb
1
1
22
33
4
class Users::DashboardController < UsersController
5 def index; end
6end
7

Karena saya ingin membuat tampilan login yang berbeda antara Admin dengan User. Saya perlu mengaturnya pada application_controller.rb.3

FILEapp/controllers/application_controller.rb
1
1
22
33
44
55
66
77
88
99
1010
1111
1212
1313
14
class ApplicationController < ActionController::Base
15 layout :layout_by_resource
16
17 private
18
19 def layout_by_resource
20 if devise_controller? && resource_name == :admin
21 'admins_devise'
22 else
23 'users'
24 end
25 end
26end
27

Saya juga membuat homepage_controller.rb untuk menghandle halaman Homepage yang saya letakkan pada direktori public/

FILEapp/controllers/public/homepage_controller.rb
1
1
22
33
4
class Public::HomepageController < ApplicationController
5 def index; end
6end
7

Serta halaman About dan Contact.

FILEapp/controllers/public/about_controller.rb
1
1
22
33
4
class Public::AboutController < ApplicationController
5 def index; end
6end
7
FILEapp/controllers/public/contact_controller.rb
1
1
22
33
4
class Public::ContactController < ApplicationController
5 def index; end
6end
7

Langsung saja membuat action :index, yang nantinya akan digunakan untuk menampilkan text sederhana pada view template.

Route

Kemudian, untuk routingnya akan seperti ini.

FILEconfig/routes.rb
1
1
22
33
44
55
66
77
88
99
1010
1111
1212
1313
1414
1515
1616
1717
1818
1919
2020
2121
2222
2323
2424
25
Rails.application.routes.draw do
26 # Root
27 root to: "public/homepage#index"
28
29 # Public
30 scope module: :public do
31 resources :about
32 resources :contact
33 end
34
35 # Admins
36 devise_for :admins
37 namespace :admins do
38 root to: "dashboard#index"
39 resources :dashboard, only: %w[index]
40 end
41
42 # Users
43 devise_for :users
44 namespace :users do
45 root to: "dashboard#index"
46 resources :dashboard, only: %w[index]
47 end
48end
49

Pada block Public, saya menggunakan scope karena ingin membuat url yang singkat, seperti ini.

http://localhost:3000/about

Kalau menggunakan namespace maka url yang dihasilkan akan seperti ini.

http://localhost:3000/public/about

Maka dari itu, saya menggunakan scope untuk controller yang berada pada module Public

Selanjutnya ke view template.

View

Berikut ini struktur direktorinya.

├─ app/
│  ├─ assets/
│  ├─ channels/
│  ├─ controllers/
│  ├─ helpers/
│  ├─ jobs/
│  ├─ mailers/
│  ├─ models/
│  └─ views/
│     ├─ admins/
│     │  └─ dashboard/
│     │     └─ index.html.erb
│     ├─ layouts/
│     │  ├─ admins/
│     │  │  └─ _nav.html.erb
│     │  ├─ users/
│     │  │  └─ _nav.html.erb
│     │  ├─ admins.html.erb
│     │  ├─ admins_devise.html.erb
│     │  ├─ application.html.erb
│     │  ├─ mailer.html.erb
│     │  ├─ mailer.text.erb
│     │  └─ users.html.erb
│     ├─ public/
│     │  ├─ about/
│     │  │  └─ index.html.erb
│     │  ├─ contact/
│     │  │  └─ index.html.erb
│     │  └─ homepage/
│     │     └─ index.html.erb
│     └─ users/
│        └─ dashboard/
│           └─ index.html.erb
│
├─ ...
...

Berikut ini isi dari file-file view tersebut.

Kita mulai dari layouts/.

FILEapp/views/layouts/application.html.erb
1
1
22
33
44
55
66
77
88
99
1010
1111
1212
1313
1414
1515
1616
17
<!DOCTYPE html>
18<html>
19 <head>
20 <title>BlogSpot</title>
21 <%= csrf_meta_tags %>
22 <%= csp_meta_tag %>
23
24 <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
25 <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
26 </head>
27
28 <body>
29 <%= render 'layouts/users/nav' %>
30 <%= yield %>
31 </body>
32</html>
33
FILEapp/views/layouts/admins.html.erb
1
1
22
33
44
55
66
77
88
99
1010
1111
1212
1313
1414
1515
1616
17
<!DOCTYPE html>
18<html>
19 <head>
20 <title>Admin - BlogSpot</title>
21 <%= csrf_meta_tags %>
22 <%= csp_meta_tag %>
23
24 <%= stylesheet_link_tag 'admins', media: 'all', 'data-turbolinks-track': 'reload' %>
25 <%= javascript_include_tag 'admins', 'data-turbolinks-track': 'reload' %>
26 </head>
27
28 <body>
29 <%= render 'layouts/admins/nav' %>
30 <%= yield %>
31 </body>
32</html>
33
FILEapp/views/layouts/users.html.erb
1
1
22
33
44
55
66
77
88
99
1010
1111
1212
1313
1414
1515
1616
17
<!DOCTYPE html>
18<html>
19 <head>
20 <title>User - BlogSpot</title>
21 <%= csrf_meta_tags %>
22 <%= csp_meta_tag %>
23
24 <%= stylesheet_link_tag 'users', media: 'all', 'data-turbolinks-track': 'reload' %>
25 <%= javascript_include_tag 'users', 'data-turbolinks-track': 'reload' %>
26 </head>
27
28 <body>
29 <%= render 'layouts/users/nav' %>
30 <%= yield %>
31 </body>
32</html>
33

Saya membuat halaman login yang berbeda antara Admin dengan User.

Halamn login untuk User akan menggunakan template dari Devise, sedangkan Admin, akan saya custom sendiri. Seperti di bawah ini.

FILEapp/views/layouts/admins_devise.html.erb
1
1
22
33
44
55
66
77
88
99
1010
1111
1212
1313
1414
1515
1616
1717
1818
1919
2020
2121
2222
2323
2424
2525
2626
2727
2828
2929
3030
3131
3232
3333
3434
3535
3636
3737
3838
3939
4040
4141
4242
4343
4444
45
<!DOCTYPE html>
46<html>
47 <head>
48 <title>Admin - BlogSpot</title>
49 <%= csrf_meta_tags %>
50 <%= csp_meta_tag %>
51
52 <%= stylesheet_link_tag 'admins', media: 'all', 'data-turbolinks-track': 'reload' %>
53 <%= javascript_include_tag 'admins', 'data-turbolinks-track': 'reload' %>
54 </head>
55
56 <body>
57 <%= render 'layouts/admins/nav' %>
58
59 <!-- Admin Custom Sign In -->
60 <% if controller_name == 'sessions' %>
61 <h2>Hello, Admin</h2>
62 <% elsif controller_name == 'registrations' %>
63 <h2>Become an Admin</h2>
64 <% end %>
65 <div>
66 <% if controller_name == 'sessions' %>
67 <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
68 <%= f.email_field :email, autofocus: true, autocomplete: "email", class: "", placeholder: "Email" %>
69 <%= f.password_field :password, autocomplete: "current-password", class: "", placeholder: "Password" %>
70 <%= f.submit "Sign In", class: "" %>
71 <% end %>
72 <% elsif controller_name == 'registrations' %>
73 <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
74 <%= f.email_field :email, autofocus: true, autocomplete: "email", class: "", placeholder: "Email" %>
75 <%= f.password_field :password, autocomplete: "current-password", class: "", placeholder: "Password" %>
76 <%= f.submit "Sign Up", class: "" %>
77 <% end %>
78 <% end %>
79 </div>
80 <div>
81 <% unless controller_name == 'registrations' %>
82 <%= link_to "Become an Admin", new_admin_registration_path, class: "" %>
83 <% end %>
84 </div>
85 <!-- END Admin Custom Sign In -->
86
87 </body>
88</html>
89

Pada keempat file view template di atas, saya menambahkan render partial untuk menu navigasi.

Saya juga mengarahkan stylesheet_link_tag dan javascript_include_tag pada masing-masing direktori asset (application, users, admins) yang nantinya akan saya tambahkan setelah selesai dengan strukturl html.

Oke, selanjutnya file render partial untuk menu navigasi.

FILEapp/views/layouts/admins/_nav.html.erb
1
1
22
33
44
55
66
77
88
99
1010
1111
1212
1313
1414
15
<nav>
16 <% unless controller_name == 'homepage' && action_name == 'index' %>
17 <%= link_to "Homepage", root_path %> |
18 <% end %>
19 <%= link_to "About", about_index_path, class: "#{'active' if controller_name == 'about'}" %> |
20 <%= link_to "Contact", contact_index_path, class: "#{'active' if controller_name == 'contact'}" %> |
21 <% if admin_signed_in? %>
22 <%= link_to "Admin Dashboard", admins_root_path, class: "#{'active' if controller_name == 'dashboard'}" %> |
23 <%= link_to "Log Out", destroy_admin_session_path, method: :delete %>
24 <% else %>
25 <%= link_to "Log In", admin_session_path, class: "#{'active' if controller_name == 'sessions'}" %>
26 | <%= link_to "User?", user_session_path %>
27 <% end %>
28</nav>
29
FILEapp/views/layouts/users/_nav.html.erb
1
1
22
33
44
55
66
77
88
99
1010
1111
1212
1313
1414
1515
1616
1717
18
<nav>
19 <% unless controller_name == 'homepage' && action_name == 'index' %>
20 <%= link_to "Homepage", root_path %> |
21 <% end %>
22 <%= link_to "About", about_index_path, class: "#{'active' if controller_name == 'about'}" %> |
23 <%= link_to "Contact", contact_index_path, class: "#{'active' if controller_name == 'contact'}" %> |
24 <% if user_signed_in? %>
25 <%= link_to "User Dashboard", users_root_path, class: "#{'active' if controller_name == 'dashboard'}" %> |
26 <%= link_to "Log Out", destroy_user_session_path, method: :delete %>
27 <% elsif admin_signed_in? %>
28 <%= link_to "Admin Dashboard", admins_root_path, class: "#{'active' if controller_name == 'dashboard'}" %> |
29 <%= link_to "Log Out", destroy_admin_session_path, method: :delete %>
30 <% else %>
31 <%= link_to "Log In", user_session_path, class: "#{'active' if controller_name == 'sessions'}" %>
32 | <%= link_to "Admin?", admin_session_path %>
33 <% end %>
34</nav>
35

Selanjutnya Homepage.

FILEapp/views/public/homepage/index.html.erb
1
1
22
33
44
55
66
77
88
99
10
<header>
11 <h1>Homepage</h1>
12</header>
13
14<div>
15 <p>
16 => <%= controller_name %>#<%= action_name %>
17 </p>
18</div>
19

Halaman About dan Contact.

FILEapp/views/public/about/index.html.erb
1
1
22
33
44
55
66
77
88
99
10
<header>
11 <h1>About</h1>
12</header>
13
14<div>
15 <p>
16 => <%= controller_name %>#<%= action_name %>
17 </p>
18</div>
19
FILEapp/views/public/contact/index.html.erb
1
1
22
33
44
55
66
77
88
99
10
<header>
11 <h1>Contact</h1>
12</header>
13
14<div>
15 <p>
16 => <%= controller_name %>#<%= action_name %>
17 </p>
18</div>
19

Kemudian, halan Dashboard untuk Admin dan User.

FILEapp/views/admins/dashboard/index.html.erb
1
1
22
33
44
55
66
77
8
<header>
9 <h1>DashBoard Admin</h1>
10</header>
11
12<div>
13 Admin: <%= current_admin.email %>
14</div>
15
FILEapp/views/users/dashboard/index.html.erb
1
1
22
33
44
55
66
77
8
<header>
9 <h1>DashBoard User</h1>
10</header>
11
12<div>
13 User: <%= current_user.email %>
14</div>
15

Sekarang tinggal Stylesheet dan Javascript.

├─ app/
│  ├─ assets/
│  │  ├─ config/
│  │  ├─ images/
│  │  ├─ javascripts/
│  │  │  ├─ admins/
│  │  │  │  └─ custom.js
│  │  │  ├─ channels/
│  │  │  ├─ users/
│  │  │  │  └─ custom.js
│  │  │  ├─ admins.js
│  │  │  ├─ application.js
│  │  │  ├─ cable.js
│  │  │  └─ users.js
│  │  └─ stylesheets/
│  │     ├─ admins/
│  │     │  └─ custom.css
│  │     ├─ users/
│  │     │  └─ custom.css
│  │     ├─ admins.css
│  │     ├─ application.css
│  │     └─ users.css
│  ├─ ...
│  ├─ ...
...

Mengikuti struktur direktori di atas.

Javascript Assets

Pada javascripts/application.js tambahkan user.js. Karena saya akan menggunakan sebagai satu kesatuan assets.

FILEapp/assets/javascripts/application.js
1
1
22
33
44
5
//= require rails-ujs
6//= require activestorage
7//= require turbolinks
8//= require users
9

Lalu pada masing-masing file Javascript untuk Admin dan User, tambahkan rails-ujs agar Devise dapat Logout.

Kalau tidak menambahkan ini, Devise akan mengalami routing error saat melakukan Logout.

FILEapp/assets/javascripts/admins.js
1
1
2
//= require rails-ujs
3
FILEapp/assets/javascripts/users.js
1
1
2
//= require rails-ujs
3

Untuk admins/custom.js dan users/custom.js digunakan untuk Javascript buatan kita sendiri. Namun karena masih kosong, jadi tidak saya contohkan.

Stylesheet Assets

Pada stylesheets/application.css tambahkan user.css. Karena saya akan menggunakan sebagai satu kesatuan assets.

Saya menghapus *=require file yang lain agar file stylesheet tidak saling tumpang tindih dan dipanggil dimana-mana.

FILEapp/assets/stylesheets/application.css
1
1
22
33
4
/*
5 *= require users
6 */
7

Kemudian pada admins.css dan users.css saya akan mengarahkan asset pada custom.css di masing-masing direktori.

File custom.css inilah yang nantinya akan digunakan apabila ingin mengkostumisasi style pada Admin atau User view.

FILEapp/assets/stylesheets/admins.css
1
1
2
@import 'admins/custom.css';
3
FILEapp/assets/stylesheets/users.css
1
1
2
@import 'users/custom.css';
3

Selanjutnya, isi dari ../admins/custom.css dan ../users/custom.css.

Untuk contoh kali ini, saya membuat style antar Admin dan User menjadi terlihat serupa.

Namun, pada project yang sesungguhnya, kedua file ini akan memiliki isi yang berbeda, sesuai dengan keperluan.

FILEapp/assets/stylesheets/admins/custom.css
1
1
22
33
44
55
66
77
88
99
1010
1111
1212
1313
1414
1515
1616
1717
1818
1919
2020
2121
22
h1,h2 {
23 color: #c52f24;
24}
25
26div {
27 margin: 5px;
28}
29
30a {
31 color: #c52f24;
32 text-decoration: none;
33 border-bottom: 1px dotted #c52f24;
34}
35
36nav {
37 margin: 5px;
38}
39
40.active {
41 font-weight: bold;
42}
43
FILEapp/assets/stylesheets/users/custom.css
1
1
22
33
44
55
66
77
88
99
1010
1111
1212
1313
1414
1515
1616
1717
1818
1919
2020
2121
22
h1,h2 {
23 color: #c52f24;
24}
25
26div {
27 margin: 5px;
28}
29
30a {
31 color: #c52f24;
32 text-decoration: none;
33 border-bottom: 1px dotted #c52f24;
34}
35
36nav {
37 margin: 5px;
38}
39
40.active {
41 font-weight: bold;
42}
43

Selanjutnya, saya perlu untuk menambahkan konfigurasi tambahan untuk Precompile Additional Assets, karena saya sudah membuat custom assets untuk admins dan users.

Buka file config/initializers/assets.rb.

FILEconfig/initializers/assets.rb
1
1
22
33
44
5
# Precompile additional assets.
6# application.js, application.css, and all non-JS/CSS in the app/assets
7# folder are already added.
8Rails.application.config.assets.precompile += %w( admins.js admins.css users.js users.css )
9

Uncomment dan tambahkan users.js dan users.css.

Oke, saya rasa, sudah semuanya.

Sekarang web aplikasi siap untuk di jalankan.

$ rails s

Kira-kira, seperti inilah hasilnya.

gambar_2

Gambar 2 - Public Page

gambar_3

Gambar 3 - User Login, Register Dashboard

gambar_4

Gambar 4 - Admin Login dan Register

Selesai!

Kerangka untuk membuat Web Aplikasi yang memiliki Halaman Public, Admin Dashboard dan User Dashboard sudah selesai.

Teman-teman dapat melihat source codenya pada halaman GitHub di sini.

Mudah-mudahan dapat bermanfaat.

Terima kasih.

(^_^)

Referensi

  1. github.com/plataformatec/devise
    Diakses tanggal: 2019/12/15

  2. guides.rubyonrails.org/routing.html#controller-namespaces-and-routing
    Diakses tanggal: 2019/12/15

  3. github.com/plataformatec/devise/wiki/How-To:-Create-custom-layouts
    Diakses tanggal: 2019/12/15