樣版(Template)


render 與 redirect_to 中看過,某個動作執行過後,預設執行的render方法會尋找app/views資料夾下,與控制器同名資料夾下的樣版檔案來呈現畫面。例如若請求tests/some,在動作處理過後,預設會尋找app/views/tests資料夾下some.html.erb檔案來呈現畫面,tests資料夾名稱是控制器名稱,some名稱是動作名稱,html.erb表示用內嵌Ruby程式碼(Embedded Ruby)的方式,產生HTML格式的回應,你也可以用xml.erb,表示用嵌入式Ruby程式碼來產生XML格式,使用json.erb、csv.erb、js.erb也是同樣的道理。

Erb是Rails預設的樣版引擎,或稱為樣版處理器(Template Handler),先前看過<%= %>與<% %>,都是Erb標籤,<%= %>用來將=之後的值、方法傳回值或運算結果等輸出為回應的一部份,如果只是運算流程,不需要輸出為回應,則可以使用<% %>。例如 基本 CRUD 程式 中看過的片段:

<ul>
  <% @pages.each do |page| %>
    <li>
      <%= link_to page.title, page.url %>
      <%= link_to "Details", :controller => 'bookmarks', :action => 'show', :id => page %>
      <%= link_to "Edit", :controller => 'bookmarks', :action => 'edit', :id => page %>
      <%= link_to "Delete", :controller => 'bookmarks', :action => 'destroy', :id => page %>
    </li>
  <% end %>
</ul>

雖然說內嵌Ruby程式碼,不過要寫註解時,並不能這麼寫(%與#分開):

<% # print self.class %>

#之後會整個被忽略,所以就看不到%>。你要嘛就換行:

<%
    # print self.class
%>

要嘛就使用<%#作開頭(%#連在一起)。例如:

<%# print self.class %>

Rails也內建Builder樣版引擎,用來產生XML格式的回應,只要檔名以.builder結尾。例如在 respond_to 與 respond_with 中看過:

  • index.xml.builder
xml.pages do                                  # 根節點 <pag es></pages>
    @pages.each do |page|
        xml.page do                           # 節點 <page></page>
           xml.title page.title               # 節點 <title></title>
           xml.url page.url                   # 節點 <url></url>
           xml.description page.description   # 節點 <description></description>
        end
    end
end

Action View Base 中可以看到一些Builder的例子(若對產生XML的原理有興趣,可以參考 BasicObject)。

你可以將一些共用的樣版內容抽取出來,成為區部樣版。例如若在樣版檔案中:

<%= render "navigation" %>


那麼與樣版檔案所在資料夾下_navigation.html.erb的內容會被加入目前樣版檔案,注意檔案一開頭有個底線,這稱為區部樣版,如果要使用路徑。例如:

<%= render "shared/navigation" %>

那會尋找app/views/shared資料夾下_navigation.html.erb檔案。

假設樣版中原本有個呈現錯誤訊息的片段:

<% if @errors %>
    <ul style="color: rgb(255, 0, 0);">
    <% @errors.each do |error| %>
        <li><%= error %></li>
    <% end %>
    </ul>
<% end %>

這是個通用的錯誤呈現片段,如果你想要將這個片段抽出為區部樣版_errors.html.erb,不過有時錯誤訊息並不一定是放在實例變數中,因此你的區部樣版設計為:

  • _errors.html.erb
<% if errors %>
    <ul style="color: rgb(255, 0, 0);">
    <% errors.each do |error| %>
        <li><%= error %></li>
    <% end %>
    </ul>
<% end %>

這個區部樣版中有個區域變數errors,可以在繪製區部樣版時,使用:locals指定區部樣版中的區域變數值。例如:

<%= render :partial => "errors", :locals => { :errors => @errors } %>

每個區部樣版都會有個區域變數與區部樣版本身名稱同名。例如:

<%= render :partial => "address", :object => @office_addr %>

在_address.html.erb中,會有個區域變數address,可以使用:object指定這個變數參考至哪個物件。如果你有個模型物件,也可以有簡寫的方式:

<%= render @address %>

@address必須是ActiveRecord::Base實例,可透過model_name取得名稱,假設模型為Address,以上會尋找_address.html.erb,並將其中區域變數address設定為@address參考的物件。

如果你有個樣版內容:

  • index.html.erb
<h1>Bookmark Online</h1>
<ul> 
  <% @pages.each do |page| %>
    <li>
      <%= link_to page.title, page.url %>
      <%= link_to "Detail", bookmark_path(page), :method => :get %>
      <%= link_to "Edit", edit_bookmark_path(page), :method => :get %>
      <%= link_to "Delete", bookmark_path(page), :method => :delete %>
    </li>
  <% end %>
</ul>
<%= link_to "New", new_bookmark_path %>

當中使用了each方法來迭代每筆資料要呈現的內容。你可以撰寫一個:

  • _page.html.erb
<li>
  <%= link_to page.title, page.url %>
  <%= link_to "Detail", bookmark_path(page), :method => :get %>
  <%= link_to "Edit", edit_bookmark_path(page), :method => :get %>
  <%= link_to "Delete", bookmark_path(page), :method => :delete %>
</li>

然後原樣版就可以改為:
  • index.html.erb
<h1>Bookmark Online</h1>
<ul> 
  <%= render :partial => "page", :collection => @pages %>
</ul>
<%= link_to "New", new_bookmark_path %>

_page.html.erb中的區域變數page會自動設為@pages.each每次迭代的物件,你也可以自行設定每次迭代後的物件,由哪個區域變數名稱參考。例如:

  • index.html.erb
<h1>Bookmark Online</h1>
<ul> 
  <%= render :partial => "page", :collection => @pages, :as => :bookmark %>
</ul>
<%= link_to "New", new_bookmark_path %>

如此一來,每次迭代的物件,將在_page.html.erb中由bookmark名稱參考,因此區域樣版要改為:

  • _page.html.erb
<li>
  <%= link_to bookmark.title, bookmark.url %>
  <%= link_to "Detail", bookmark_path(bookmark), :method => :get %>
  <%= link_to "Edit", edit_bookmark_path(bookmark), :method => :get %>
  <%= link_to "Delete", bookmark_path(bookmark), :method => :delete %>
</li>