Kalau kamu cukup lama bermain Ruby on Rails dan terbiasa merujuk ke dokumentasi routing yang bisa dibaca di https://guides.rubyonrails.org/routing.html#resource-routing-the-rails-default, istilah RESTful route saya rasa sudah bukan hal yang asing. Pada intinya adalah route url yang ada di aplikasi kita disesuaikan dengan HTTP verb untuk mengolah resource yang kita punya (CRUD). Resource di sini biasanya kelas model yang merepresentasikan tabel di database.
Misal saya definisikan routing semacam ini.
1 2 3 |
Rails.application.routes.draw do resources :users end |
Otomatis Rails membuatkan routing untuk kita yang memenuhi standar RESTful di Rails seperti ini.
Bisa dilihat di kolom Path, url yang dihasilkan simpel dan nice.
Controller-nya juga sesuai standar RESTful, punya 7 action kesayangan kita untuk CRUD.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class UsersController < ApplicationController def index end def show end def new end def create end def edit end def update end def destroy end end |
Dengan semakin berkembangnya aplikasi, dari sisi bisnis membutuhkan fitur untuk membuat user jadi admin dan sebaliknya, dari yang tadinya admin jadi user biasa. Biasanya, biasanya nih, yang langsung terpikir adalah kita membuat action baru di controller seperti ini.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class UsersController < ApplicationController ... ... def grant_admin current_resource.update(admin: true) redirect_to users_path end def revoke_admin current_resource.update(admin: false) redirect_to users_path end private def current_resource User.find_by(id: params[:id]) end end |
Secara feeling kita akan menambahkan action baru di UsersController
karena model yang terlibat adalah User
, dalam hal ini kita ubah kolom admin
dari false ke true untuk mengangkat seorang user menjadi admin.
Dengan penambahan ini berarti kita perlu buat routing baru. Maka kita update jadi seperti ini.
1 2 3 4 5 6 |
Rails.application.routes.draw do resources :users do put :grant_admin, on: :member put :revoke_admin, on: :member end end |
Routing yang baru bentuknya seperti ini.
Dan view yang kita buat seperti ini. Perhatikan baris yang saya highlight, menunjukkan pemanggilan path custom yang dibuat.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<div class="columns is-centered"> <div class="column is-10-desktop"> <table class="table is-hoverable"> <tr> <td>Name</td> <td>Username</td> <td>Email</td> <td>Role</td> <td>Action</td> </tr> <% @users.each do |u| %> <tr> <td><%= u.name %></td> <td><%= u.username %></td> <td><%= u.email %></td> <td><%= u.admin? ? "Admin" : "Reguler" %></td> <td> <div class="buttons"> <%= button_to "make admin", grant_admin_user_path(u), method: :put, class: 'button is-small is-success' %> <%= button_to "revoke admin", revoke_admin_user_path(u), method: :put, class: 'button is-small is-warning' %> </div> </td> </tr> <% end %> </table> </div> </div> |
Http method yang dipakai adalah put karena kita mengupdate data.
Make sense kan?
Kita mau nge-update user, jadi kita buat action baru di UsersController
dan tambahkan routing baru. Yang jadi “agak-agak” adalah bentuk url-nya yang tidak lagi memenuhi standar RESTful routing di Rails.
Secara teknis dan bisnis memang tidak ada yang salah. Dan lagi pula dalam hal ini tidak ada benar atau salah, hanya preferensi masing-masing. Kalau saya pribadi, lebih prefer sebisa mungkin tetap menggunakan RESTful routing yang pola path url-nya seperti tabel pertama.
Terus caranya gimana?
Dimulai dengan memandang resource.
Kita terbiasa memandang resource sebagai kata benda, dalam hal ini model, yang memiliki representasi tabel database. Contohnya User, Post, Product, Category, Order dll dll. Sehingga ketika ada kasus perlu seorang user dikasih hak akses sebagai admin atau diambil hak aksesnya, otomatis resource yang kita pikirkan adalah user dengan kelas User sebagai modelnya. Dan karena kita sudah tidak mungkin menggunakan method standar RESTful di Rails (sudah terpakai), kita memilih menggunakan route tambahan (grant_admin dan revoke_admin).
Sekarang bagaimana kalau cara pandangnya kita ubah. Tidak hanya user yang dipandang sebagai resource, tapi admin adalah sebuah resource juga. Dengan cara pandang baru ini berarti kita bisa me-mapping proses grant admin sebagai proses membuat admin dan proses revoke admin sebagai proses menghapus admin.
Dari sini sudah kita dapat kata membuat dan menghapus yang bisa kita sangkutkan dengan method create dan destroy. Nah jadi bisa kita buat AdminsController
yang punya method create dan destroy.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class AdminsController < ApplicationController def create current_resource.update(admin: true) redirect_to users_path end def destroy current_resource.update(admin: false) redirect_to users_path end private def current_resource User.find_by(id: params[:user_id]) end end |
Perhatikan bahwa sekarang di method current_resource
nama parameter yang digunakan untuk mendapatkan id user bukan lagi id
tapi user_id
.
1 2 3 |
params[:id] #versus params[:user_id] |
Ini karena routes yang ada diubah menjadi nested resource seperti ini.
1 2 3 |
resources :users do resource :admins, only: [:create, :destroy] end |
Kenapa saya pilih menjadi nested resource karena resource admin bisa dibilang merupakan produk turunan dari user, dalam arti bukan resource yang berdiri sendiri.
Perhatikan juga yang digunakan kali ini adalah resource
bukan resources
. Hal ini bukan typo tapi kesengajaan karena dikasus ini kita tidak membutuhkan id admin saat path mengarah ke method destroy. Perhatikan perbandingan di bawah.
resource
akan menghasilkan /users/:user_id/admins
untuk destroy (http delete)
resources
akan menghasilkan /users/:user_id/admins/:id
untuk destroy (http delete)
Biar lebih jelas, berikut adalah path baru yang dibuat.
Dari sini kita bisa hapus method grant_admin
dan revoke_admin
di UsersController
dan sekaligus juga hapus route-nya.
Selanjutnya sesuaikan path yang dipanggil di view jadi seperti ini.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<div class="columns is-centered"> <div class="column is-10-desktop"> <table class="table is-hoverable"> <tr> <td>Name</td> <td>Username</td> <td>Email</td> <td>Role</td> <td>Action</td> </tr> <% @users.each do |u| %> <tr> <td><%= u.name %></td> <td><%= u.username %></td> <td><%= u.email %></td> <td><%= u.admin? ? "Admin" : "Reguler" %></td> <td> <div class="buttons"> <%= button_to "make admin", user_admins_path(u), class: 'button is-small is-success' %> <%= button_to "revoke admin", user_admins_path(u), method: :delete, class: 'button is-small is-warning' %> </div> </td> </tr> <% end %> </table> </div> </div> |
Gimana menurut kamu?
Kalau saya pribadi jelas lebih memilih pendekatan yang baru ini karena controller menjadi lebih clean. Makin banyak fitur yang terkait dengan user kalau pakai pendekatan yang pertama makin nambah kode di UsersController
sehingga kompleksitas untuk memahami peran kelas ini jadi meningkat.
Ambil contoh user bisa di-banned oleh admin. Dari pada buat method ban
di UsersController, lebih baik buat BannedUsersController
dengan method create
.
Source Code
Source code bisa dilihat di github https://github.com/agungsetiawan/restfuldon/commits/master
Lihat commit histroy, di situ bisa lihat perubahan dari non restful route ke restful route.