Nic Lin's Blog

喜歡在地上打滾的 Rails Developer

不要再佔用 controller action 與 route 來更新資料了

常見更新數據狀態的作法

一般來說,除了狀態機(AASM)變更狀態以外,有一種情況是針對這筆資料進行狀態變更,通常的設計是這樣子的,有一個按鈕,按下去之後觸發功能。

  1. 按鈕按下去,找到 route
  2. route 進到該對應的 controller action
  3. 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 的濫用

  1. 於連結中傳入 status 參數(publish/hide/move_up/move_down/…),並使用 HTTP 中 patch 動詞
  2. 於 controller 中的 update action 裡面設定 status 只接受何種狀態(如上述程式碼中的 case when )

如此一來,就不會因為變更狀態而寫了一堆 routes 與 controller action 了

你只需要維護 update 中的部分,畢竟變更狀態,就是 update 一筆資料中的相關欄位嘛

喔對,你的 routes 裡就只要一行

resources :groups

就是這麼的乾淨!

Share Comments
comments powered by Disqus