Nic Lin's Blog

喜歡在地上滾的工程師

不需在創建 new or edit path 的 helper, 交給 controller 決定吧

new or edit path helper? We can creating easy more path!

我們可能常常會需要在 view 裡面寫到 edit or new 的 link,這是我們在常見不過的一段判斷式了

<% if @project.presnet? %>
    <%= link_to 'edit', edit_project_path(@project) %>
<% else %>
    <%= link_to 'new', new_project_path %>
<% end %>

然而我們認為更棒的作法,就是把這判斷式放進 helper 裡面,只為了讓 view 看起來更簡單一些,也許可以參照這篇 StackOverFlow

例如

def render_new_or_edit_project_path(project)
    if project.present?
      link_to 'edit', edit_project_path(project)
    else
      link_to 'new', new_project_path
    end
end

然後我們在頁面只要載入這個 helper 就好了,對吧?

<%= render_new_or_edit_project_path(@project) %>

看起來一切完美,將 view 做了個 Refactor

but…轉折處就在這個 but

如果你未來繼續使用這樣的 Refactor 的方式,那麼 helper 裡面就會出現一堆

render_new_or_edit_xxxxxx_path 的這種鬼東西

或許你會認為可以把他用動態(dynamic)的方式生成,但得考慮到

  1. 如果遇到有 namespace 的 routes ?
  2. evalsend,是不是有 security issue 的問題? ,也許你會說使用 public_send,但畢竟這都是 ruby 的 meta-programming,對未來維護的時候,或許不是這麼好找尋?

那麼這個 helper 看起來又不是這麼好用了,變成只專門 for 某個頁面的方法,這樣真的是 refactor 嗎?

更好的方法

面對 new or edit 這個方法,我認為有更好的選擇

  • 使用 simple_form
  • 將 new or edit 交給 object 決定

我們可以統一把 path 交給某個 action 處理,個人偏好使用 newedit (如果你有更好的作法可以試試看自己設置的 action )

所以我們不需要花時間在判斷上面,只要連結一律導向 new_project_path

然後在 controller 裡面寫

def new
 @project = Project.find_by_id(params[:id]) || Project.new
end

如此一來,在你的 simple_form_for 裡面

<% simple_form_for @project do |f| %>
...
<% end %>

他就能在 submit 時,自動選擇「new(新建)」或是「update(更新)」行為

因為這個自動選擇,是取決於你的 Object, Simple Form 會在將 object 傳送進來時,決定你的 action 以及 method

我們在 simple form 傳進去的 object ,在這裡定義為 record

action = record.respond_to?(:persisted?) && record.persisted? ? :edit : :new

也因為在這裡就做好選擇,所以一切是這麼智能,如果你想更了解,請詳見 simple form 的 source code 是如何因 object 決定行為

補充一下,如何知道這個 object 是已經存在?或是剛 new 出來的?

Project.new.new_record? # true
Project.new.persisted? # false

# 假定數據id為1的 project 存在的情況

Project.find(1).new_record? # false
Project.find(1).persisted? # true

如此一來,就不用寫這麼多的 new_or_edit_path 的 helper 啦!

comments powered by Disqus