about 11 years ago

這一篇的重點是 Partial 的設計

7. Move code to Partial

什麼時候應該將把程式碼搬到 Partial 呢?

  • long template | code 超過兩頁請注意
  • highly duplicated | 內容高度重複
  • indepdenent blocks | 可獨立作為功能區塊

常見情境:

  • nav/user_info
  • nav/admin_menu
  • vendor_js/google_analytics
  • vendor_js/disqus_js
  • global/footer

8. Use presenter to clean the view ( 使用 Presenter 解決 logic in view 問題)

在前一章節,我們介紹過 view 裡面常被迫出現這種 code

<%= if profile.has_experience? && profile.experience_public? %>
  <p><strong>Experience:</strong> <%= user_profile.experience %></p>
<% end %>

Presenter 這個設計手法最近很少在 Rails 界被提到,是因為 Presenter 很常被濫用。不過在現在這段程式碼的狀況,其實你可以用 Presenter 實作一點整理:

class ProfilePresenter < ::Presenter
  def with_experience(&block)
    if profile.has_experience? && profile.experience_public?
      block.call(view)
    end
  end
end

然後就可以生出這麼優美的 View

<% user_profile.with_experience do %>
  <p><strong>Experience:</strong> <%= user_profile.experience %></p>
<% end %><% user_profile.with_hobbies do %>
  <p><strong>Hobbies:<strong> <%= user_profile.hobbies %></p>
<% end %>

9. Cache Digest

Logic in View 有兩種最常出現的 case: if / elsefor / each。前者我們可以用 Helper 閃掉,後者卻幾乎無法有其他方式取代。

這是一個經典的迴圈 View:

<% @project do %>
  aaa
  <% @todo do %>
    bbb
    <% @todolist do %>
      ccc
    <% end %>
  <% end %>
<% end %>

通常提高效率的方式,只有「進行 Cache」。

<% cache @project do %>
  aaa
  <% cache @todo do %>
    bbb
    <% cache @todolist do %>
      ccc
    <% end %>
  <% end %>
<% end %>

但是進行 cache,又會遇到 cache invalid 的問題,比如要改 todolist 迴圈裡的 ccc,就非常困難。於是,我們又會改變思路,在 cache 上面加上版本號,如

<% cache [v15,@project] do %>
  aaa
  <% cache [v10,@todo] do %>
    bbb
    <% cache [v45,@todolist] do %>
      zzz
    <% end %>
  <% end %>
<% end %>

但加上版本號以後,還是有其他的問題,很多時候 view 是被 partial 層層嵌套,如上面這個例子,當我們把版本 bump 到 v46,還必須去找出所有上層的 v10,去改成 v11,很是麻煩。

Rails 的創始人 DHH,開發了一個 gem : cache_digest。現在已正式內建在 Rails4 中,完美了解決這個問題。cache_digest 的思路是會自動計算 cache helper 裡的 view 的 checksum :

md5_of_this_view
  <% cache @todolist do %>
    zzz
  <% end %>

因此,安裝了 cache_digest 的 Rails project,在遇到這種狀況時:

<% cache @project do %>
  aaa
  <% cache @todo do %>
    bbb
    <% cache @todolist do %>
      ccc
    <% end %>
  <% end %>
<% end %>

可以自動 invalid cache,不需要作額外的 hack,很是方便。

10. Cells

在設計某些 Profile 頁,會遇到必須針對某些 Object 進行 diplay related information 的狀況。如 User Profile:

程式碼會長這樣:

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    @recent_posts = @user.recent_posts.limit(5)
    @favorite_posts = @user.favorite_posts.limit(5)
    @recent_comments = @user.comments.limit(5)
  end
end
<%= render :partial => "users/recent_post", :collection => @recent_posts %>
<%= render :partial => "users/favorite_post", :collection => @favorite_posts %>
<%= render :partial => "users/recent_comment", :collection => @recent_comments %>

這算是一個相對 OK 的設計。但後續可能會撞到一些奇怪的問題:

  • 如果要對個別 block 進行 cache,而且每一個 block 需要被 cache 不等時間,如 3/5/7 hours
  • 每個 block 內的 data set 必須要進行一些加工。

這時候整個 controller 和 view 就會被污染到無法想像,而且效率非常低下。

這時候可以引進外部的 Gem 進來整理,不需拘泥於原始的 Controller / View 架構,而這個 Gem 就是 cells

Cells 的想法是開發者並不需要強行在一層的 MVC 架構裡,在 Controller 把一次事情做完(撈資料,處理資料)。開發者可以使用 Cells 把這些複雜邏輯拆成一個一個獨立的邏輯元件(componet)去實作。而這些 component 是可以被複用、被 Cahce、可以被測試的。

簡而言之,在用 Cells 時,你是可以把 Cells 想成這樣的:

  • 可以複用可以 Cache 的 Partial
  • 這個可以被 Cache 的 Partial,自己有一個迷你的 Controller 和 View

同時 Cells 可以讓你把 cache 的條件拆成非常的漂亮

app/controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
  end
end

app/views/users/show.html.erb
   <%= render_cell :user, :rencent_posts, :user => :user %>
   <%= render_cell :user, :favorite_posts, :user => :user %>
   <%= render_cell :user, :recent_comments, :user => :user %>
app/cells/user_cell.rb
class UserCell < Cell::Rails

  cache :recent_posts, :expires_in => 1.hours
  cache :favorite_posts, :expires_in => 3.hours
  cache :recent_comments, :expires_in => 5.hours
 
  def recent_posts(args)
    @user = args[:user]
    @recent_posts = @user.recent_posts.limit(5)
    render
  end
  
  def favorite_posts(args)
    @user = args[:user]
    @favorite_posts = @user.favorite_posts.limit(5)
    render 
  end
  
  def recent_comments(args)
    @user = args[:user]
    @recent_comments = @user.comments.limit(5)
    render
  end
end

我之前曾經寫過一個 Cells 系列,如果你有興趣深入把玩 Cells 的話,以下是系列連結:


Maintainable Rails View 系列文目錄

← Maintainable Rails View (1) Maintainable Rails View (3) →
 
comments powered by Disqus