常見更新數據狀態的作法
一般來說,除了狀態機(AASM)變更狀態以外,有一種情況是針對這筆資料進行狀態變更,通常的設計是這樣子的,有一個按鈕,按下去之後觸發功能。
- 按鈕按下去,找到 route
- route 進到該對應的 controller action
- action 內觸發 ActiveRecord 裡的 method 進行數據 update
比方說 user 可以創建很多 group,但我想要讓 group 在畫面顯示的時候可以由後台進行控制,公開或是隱藏
所以這時候就會出現
def publish
@group.publish!
end
def hide
@group.hide!
end
resources :groups do
member do
post :publish
post :hide
end
end
那麼如果我要控制這筆 group 的 position 呢?比方說往上一筆,往下一筆 你有可能會使用到acts_as_list這隻 gem 來完成。
那麼我的 controller 裡面豈不是又要加入這兩個 action 變成
def publish
@group.publish!
end
def hide
@group.hide!
end
def move_up
@group.move_higher # acts_as_list 裡面的 helper method
end
def move_down
@group.move_lower # acts_as_list 裡面的 helper method
end
然而 routes 也得變成這樣…
resources :groups do
member do
post :publish
post :hide
post :move_up
post :move_down
end
end
如果我在來個將 position 置頂、置底呢
move_top, move_bottom ?
沒完沒了的佔用了很多 controller 中的 action 還有其 routes
其實在上述的例子,一般新手或是以前的我都會這樣寫,那麼這個東西既不好維護,又容易讓程式碼越來越長,卻只為了更新某一筆資料的狀態或是位置?
有沒有更好的作法?
我認為這些單純更新數據的行為,在 HTTP 動詞裡面應該用 patch 而不是 post,這個 Rails guide 裡面有提到。
所以我的想法是,把所有這樣的行為,透過傳遞 params 的方式來做選擇,而這個最適合做選擇的地方,就是 CRUD 裡面的 update ,我認為既符合原意,又不浪費 action 與 routes。
首先將你的觸發連結,全部導向 update action
利用 link_to
,在 path 中夾帶 value,並且打 patch 出去
<%= link_to "公開", group_path(@group, status: "public"), method: :patch %>
<%= link_to "隱藏", group_path(@group, status: "hide"), method: :patch %>
在將 controller 裡的 update 改寫一下
def update
status = params[:status].nil? ? "update" : params[:status]
success =
case status
when "update"
@group.update(group_params)
when "publish"
@group.publish!
when "hide"
@group.hide!
else
false
end
if success
flash[:notice] = "#{@group}更新成功。"
redirect_to groups_path
else
render :edit
end
end
如此一來,你只要遵循這樣的設計模式,就可以減少 controller action 與 routes 的濫用
- 於連結中傳入 status 參數(publish/hide/move_up/move_down/…),並使用 HTTP 中 patch 動詞
- 於 controller 中的 update action 裡面設定 status 只接受何種狀態(如上述程式碼中的 case when )
如此一來,就不會因為變更狀態而寫了一堆 routes 與 controller action 了
你只需要維護 update 中的部分,畢竟變更狀態,就是 update 一筆資料中的相關欄位嘛
喔對,你的 routes 裡就只要一行
resources :groups
就是這麼的乾淨!