更多資訊請參閱 rubyonrails.org:

1 什麼是 Action View?

Action View 是 MVC 中的 V。Action Controller 和 Action View 協同運作來處理 Web 請求。Action Controller 負責與模型層(MVC)進行通訊並擷取資料。Action View 則負責使用該資料將回應主體渲染至 Web 請求。

預設情況下,Action View 範本(也簡稱為「視圖」)是使用嵌入式 Ruby (ERB) 編寫的,這允許在 HTML 文件中使用 Ruby 程式碼。

Action View 提供了許多 輔助方法,用於動態產生表單、日期和字串的 HTML 標籤。也可以根據需要將自訂輔助方法新增至您的應用程式。

Action View 可以利用 Active Model 的功能,例如 to_paramto_partial_path 來簡化程式碼。這並不表示 Action View 依賴於 Active Model。Action View 是一個獨立的套件,可以與任何 Ruby 程式庫一起使用。

2 在 Rails 中使用 Action View

Action View 範本(又名「視圖」)儲存在 app/views 目錄的子目錄中。每個控制器都有一個與其名稱相符的子目錄。該子目錄內的視圖檔案用於渲染特定的視圖,以作為對控制器動作的回應。

例如,當您使用支架產生 article 資源時,Rails 會在 app/views/articles 中產生以下檔案

$ bin/rails generate scaffold article
      [...]
      invoke  scaffold_controller
      create    app/controllers/articles_controller.rb
      invoke    erb
      create      app/views/articles
      create      app/views/articles/index.html.erb
      create      app/views/articles/edit.html.erb
      create      app/views/articles/show.html.erb
      create      app/views/articles/new.html.erb
      create      app/views/articles/_form.html.erb
      [...]

檔案名稱遵循 Rails 命名慣例。它們與相關的控制器動作共用其名稱。例如 index.html.erbedit.html.erb 等。

透過遵循此命名慣例,Rails 會在控制器動作結束時自動尋找並渲染相符的視圖,而無需您指定它。例如,articles_controller.rb 中的 index 動作會自動渲染 app/views/articles/ 目錄中的 index.html.erb 視圖。檔案的名稱和位置都很重要。

傳回給用戶端的最終 HTML 由 .html.erb ERB 檔案、包裝它的佈局範本以及 ERB 檔案可能參考的所有局部範本組合而成。在本指南的其餘部分,您會找到有關三個元件的更多詳細資訊:範本局部範本佈局

3 範本

Action View 範本可以用不同的格式編寫。如果範本檔案具有 .erb 副檔名,它會使用嵌入式 Ruby 來建置 HTML 回應。如果範本具有 .jbuilder 副檔名,它會使用 Jbuilder gem 來建置 JSON 回應。而具有 .builder 副檔名的範本則使用 Builder::XmlMarkup 程式庫來建置 XML 回應。

Rails 使用檔案副檔名來區分多個範本系統。例如,使用 ERB 範本系統的 HTML 檔案的副檔名為 .html.erb,而使用 Jbuilder 範本系統的 JSON 檔案的副檔名為 .json.jbuilder。其他程式庫也可能會新增其他範本類型和副檔名。

3.1 ERB

ERB 範本是一種使用特殊的 ERB 標籤(如 <% %><%= %>)在靜態 HTML 中加入 Ruby 程式碼的方式。

當 Rails 處理以 .html.erb 結尾的 ERB 視圖範本時,它會評估嵌入的 Ruby 程式碼,並將 ERB 標籤取代為動態輸出。該動態內容會與靜態 HTML 標記結合,形成最終的 HTML 回應。

在 ERB 範本中,可以使用 <% %><%= %> 標籤來包含 Ruby 程式碼。當您想要執行 Ruby 程式碼但不直接輸出結果(例如條件或迴圈)時,使用 <% %> 標籤(不帶 =)。標籤 <%= %> 用於產生輸出且您希望將該輸出渲染在範本中的 Ruby 程式碼,例如此範例中的模型屬性 person.name

<h1>Names</h1>
<% @people.each do |person| %>
  Name: <%= person.name %><br>
<% end %>

迴圈是使用一般嵌入標籤 (<% %>) 設定的,而名稱是使用輸出嵌入標籤 (<%= %>) 插入的。

請注意,諸如 printputs 等函式不會使用 ERB 範本渲染到視圖。因此,像這樣的情況將無法運作

<%# WRONG %>
Hi, Mr. <% puts "Frodo" %>

以上範例顯示可以在 <%# %> 標籤內於 ERB 中新增註解。

若要隱藏前導和尾隨空白,您可以將 <%- -%><%%> 交替使用。

3.2 Jbuilder

Jbuilder 是一個由 Rails 團隊維護且包含在預設 Rails Gemfile 中的 gem。它用於使用範本來建置 JSON 回應。

如果您沒有它,您可以將以下內容新增至您的 Gemfile

gem "jbuilder"

名為 jsonJbuilder 物件會自動提供給具有 .jbuilder 副檔名的範本。

以下是一個基本範例

json.name("Alex")
json.email("alex@example.com")

將產生

{
  "name": "Alex",
  "email": "alex@example.com"
}

請參閱 Jbuilder 文件以取得更多範例。

3.3 Builder

Builder 範本是 ERB 的更程式化的替代方案。它與 JBuilder 類似,但用於產生 XML,而不是 JSON。

名為 xmlXmlMarkup 物件會自動提供給具有 .builder 副檔名的範本。

以下是一個基本範例

xml.em("emphasized")
xml.em { xml.b("emph & bold") }
xml.a("A Link", "href" => "https://rubyonrails.org")
xml.target("name" => "compile", "option" => "fast")

將產生

<em>emphasized</em>
<em><b>emph &amp; bold</b></em>
<a href="https://rubyonrails.org">A link</a>
<target option="fast" name="compile" />

任何帶有區塊的方法都將被視為帶有區塊中巢狀標記的 XML 標記。例如,以下

xml.div {
  xml.h1(@person.name)
  xml.p(@person.bio)
}

會產生類似以下內容

<div>
  <h1>David Heinemeier Hansson</h1>
  <p>A product of Danish Design during the Winter of '79...</p>
</div>

請參閱 Builder 文件以取得更多範例。

3.4 範本編譯

預設情況下,Rails 會將每個範本編譯成一個方法來渲染它。在開發環境中,當您變更範本時,Rails 會檢查檔案的修改時間並重新編譯它。

當頁面的不同部分需要快取和單獨過期時,也會有片段快取。在快取指南中瞭解更多資訊。

4 局部範本

局部範本(通常簡稱為「局部」)是一種將視圖範本分解為更小、可重複使用的區塊的方法。使用局部範本,您可以將程式碼片段從主要範本擷取到單獨的較小檔案,並在主要範本中渲染該檔案。您也可以將資料從主要範本傳遞到局部檔案。

讓我們透過一些範例來看看它的運作方式

4.1 渲染局部範本

若要在視圖中渲染局部範本,您可以使用視圖內的 render 方法

<%= render "product" %>

這會在同一個資料夾中尋找名為 _product.html.erb 的檔案,以便在該視圖中渲染。按照慣例,局部檔案名稱以底線字元開頭。檔案名稱將局部範本與一般視圖區分開來。但是,在視圖中引用局部範本進行渲染時,不會使用底線。即使您從另一個目錄引用局部範本也是如此

<%= render "application/product" %>

這段程式碼會尋找並顯示位於 app/views/application/ 目錄下的名為 _product.html.erb 的部分檔案。

4.2 使用 Partial 來簡化視圖

使用 partial 的一種方式是將它們視為等同於方法。一種將細節移出視圖的方法,以便您可以更容易地掌握正在發生的事情。例如,您可能會有一個看起來像這樣的視圖:

<%= render "application/ad_banner" %>

<h1>Products</h1>

<p>Here are a few of our fine products:</p>
<% @products.each do |product| %>
  <%= render partial: "product", locals: { product: product } %>
<% end %>

<%= render "application/footer" %>

在這裡,_ad_banner.html.erb_footer.html.erb 這兩個 partial 可能包含在應用程式中的許多頁面之間共用的內容。當您專注於產品頁面時,您不需要查看這些區塊的詳細資訊。

上面的範例也使用了 _product.html.erb 這個 partial。這個 partial 包含呈現個別產品的詳細資訊,並用於呈現 @products 集合中的每個產品。

4.3 使用 locals 選項將資料傳遞給 Partials

在呈現 partial 時,您可以將資料從呈現視圖傳遞到 partial。您可以使用 locals: 選項雜湊來執行此操作。locals: 選項中的每個鍵都可作為 partial 的本地變數使用。

<%# app/views/products/show.html.erb %>

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

<%# app/views/products/_product.html.erb %>

<%= tag.div id: dom_id(my_product) do %>
  <h1><%= my_product.name %></h1>
<% end %>

「partial 的本地變數」是指在給定的 partial 中本地化,且僅在該 partial 內可用的變數。在上面的範例中,my_product 是一個 partial 的本地變數。當從原始視圖傳遞到 partial 時,它被賦予了 @product 的值。

請注意,通常我們會簡單地將這個本地變數稱為 product。我們在這裡使用 my_product 是為了將它與範例中的實例變數名稱和樣板名稱區分開來。

由於 locals 是一個雜湊,您可以根據需要傳遞多個變數,例如 locals: { my_product: @product, my_reviews: @reviews }

但是,如果樣板引用了一個沒有作為 locals: 選項的一部分傳遞到視圖的變數,該樣板將引發 ActionView::Template::Error 錯誤。

<%# app/views/products/_product.html.erb %>

<%= tag.div id: dom_id(my_product) do %>
  <h1><%= my_product.name %></h1>

  <%# => raises ActionView::Template::Error for `product_reviews` %>
  <% product_reviews.each do |review| %>
    <%# ... %>
  <% end %>
<% end %>

4.4 使用 local_assigns

每個 partial 都有一個稱為 local_assigns 的可用方法。您可以使用此方法來存取透過 locals: 選項傳遞的鍵。如果 partial 在呈現時沒有設定 :some_key,則 local_assigns[:some_key] 的值在 partial 內將會是 nil

例如,在下面的範例中,product_reviewsnil,因為 locals: 中只設定了 product

<%# app/views/products/show.html.erb %>

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

<%# app/views/products/_product.html.erb %>

<% local_assigns[:product]          # => "#<Product:0x0000000109ec5d10>" %>
<% local_assigns[:product_reviews]  # => nil %>

local_assigns 的一個用例是選擇性地傳遞一個本地變數,然後根據是否設定了本地變數,在 partial 中有條件地執行操作。例如:

<% if local_assigns[:redirect] %>
  <%= form.hidden_field :redirect, value: true %>
<% end %>

另一個來自 Active Storage 的 _blob.html.erb 的範例。這個範例會根據呈現包含此行的 partial 時是否設定了 in_gallery 本地變數來設定大小。

<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>

4.5 沒有 partiallocals 選項的 render

在上面的範例中,render 接受 2 個選項:partiallocals。但是,如果這些是您唯一需要使用的選項,您可以跳過鍵 partiallocals,僅指定值即可。

例如,而不是

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

您可以寫成

<%= render "product", product: @product %>

您也可以根據慣例使用此簡寫。

<%= render @product %>

這將在 app/views/products/ 中尋找名為 _product.html.erb 的 partial,並傳遞一個名為 product 的本地變數,其值設定為 @product

4.6 asobject 選項

預設情況下,傳遞給樣板的物件會以與樣板相同的名稱在本地變數中。因此,給定

<%= render @product %>

_product.html.erb partial 中,您將在本地變數 product 中取得 @product 實例變數,如同您已寫成

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

可以使用 object 選項來指定不同的名稱。當樣板的物件在其他地方時(例如,在不同的實例變數或本地變數中),這很有用。

例如,而不是

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

您可以寫成

<%= render partial: "product", object: @item %>

這會將實例變數 @item 指派給名為 product 的 partial 本地變數。如果您想將本地變數名稱從預設的 product 變更為其他名稱怎麼辦?您可以使用 :as 選項。

使用 as 選項,您可以為本地變數指定不同的名稱,如下所示:

<%= render partial: "product", object: @item, as: "item" %>

這等效於

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

4.7 呈現集合

視圖通常會遍歷一個集合,例如 @products,並為集合中的每個物件呈現一個 partial 樣板。這種模式已實作為一個單一方法,該方法接受一個陣列,並為陣列中的每個元素呈現一個 partial。

因此,這個用於呈現所有產品的範例

<% @products.each do |product| %>
  <%= render partial: "product", locals: { product: product } %>
<% end %>

可以用單行重寫

<%= render partial: "product", collection: @products %>

當使用集合呼叫 partial 時,partial 的個別實例可以透過一個以 partial 名稱命名的變數存取正在呈現的集合成員。在此案例中,由於 partial 是 _product.html.erb,您可以使用 product 來參考正在呈現的集合成員。

您也可以使用以下基於慣例的簡寫語法來呈現集合。

<%= render @products %>

上述假設 @products 是一個 Product 實例的集合。Rails 使用命名慣例來決定要使用的 partial 名稱,方法是查看集合中的模型名稱,在本例中為 Product。事實上,您甚至可以使用此簡寫來呈現由不同模型實例組成的集合,而 Rails 將會為集合的每個成員選擇正確的 partial。

4.8 間隔樣板

您還可以指定第二個 partial,以便在使用 :spacer_template 選項時,在主要 partial 的實例之間呈現。

<%= render partial: @products, spacer_template: "product_ruler" %>

Rails 會在每對 _product.html.erb partial 之間呈現 _product_ruler.html.erb partial(不向其傳遞任何資料)。

4.9 計數器變數

Rails 還在集合呼叫的 partial 內提供一個計數器變數。該變數以 partial 的標題命名,後面加上 _counter。例如,當呈現集合 @products 時,partial _product.html.erb 可以存取變數 product_counter。該變數會為在封閉視圖中呈現 partial 的次數建立索引,第一次呈現的值為 0

<%# index.html.erb %>
<%= render partial: "product", collection: @products %>
<%# _product.html.erb %>
<%= product_counter %> # 0 for the first product, 1 for the second product...

當使用 as: 選項變更本地變數名稱時,此方法也適用。因此,如果您執行 as: :item,計數器變數將會是 item_counter

注意:以下兩個章節「嚴格本地變數」和「使用模式比對的本地指派」是使用 partial 的更進階功能,此處包含以求完整。

4.10 使用模式比對的 local_assigns

由於 local_assigns 是一個 Hash,因此它與 Ruby 3.1 的模式比對指派運算子相容。

local_assigns => { product:, **options }
product # => "#<Product:0x0000000109ec5d10>"
options # => {}

當將 :product 以外的鍵指派給 partial 的本地 Hash 變數時,它們可以散佈到輔助方法呼叫中。

<%# app/views/products/_product.html.erb %>

<% local_assigns => { product:, **options } %>

<%= tag.div id: dom_id(product), **options do %>
  <h1><%= product.name %></h1>
<% end %>

<%# app/views/products/show.html.erb %>

<%= render "products/product", product: @product, class: "card" %>
<%# => <div id="product_1" class="card">
  #      <h1>A widget</h1>
  #    </div>
%>

模式比對指派也支援變數重新命名。

local_assigns => { product: record }
product             # => "#<Product:0x0000000109ec5d10>"
record              # => "#<Product:0x0000000109ec5d10>"
product == record   # => true

您也可以有條件地讀取變數,然後在使用 fetch 時,如果鍵不是 locals: 選項的一部分,則會回到預設值。

<%# app/views/products/_product.html.erb %>

<% local_assigns.fetch(:related_products, []).each do |related_product| %>
  <%# ... %>
<% end %>

將 Ruby 3.1 的模式比對指派與呼叫 Hash#with_defaults 結合使用,可以進行簡潔的 partial 本地預設變數指派。

<%# app/views/products/_product.html.erb %>

<% local_assigns.with_defaults(related_products: []) => { product:, related_products: } %>

<%= tag.div id: dom_id(product) do %>
  <h1><%= product.name %></h1>

  <% related_products.each do |related_product| %>
    <%# ... %>
  <% end %>
<% end %>

4.11 嚴格本地變數

Action View partial 在底層編譯成常規的 Ruby 方法。由於在 Ruby 中不可能動態建立本地變數,因此傳遞給 partial 的每個 locals 組合都需要編譯另一個版本。

<%# app/views/articles/show.html.erb %>
<%= render partial: "article", layout: "box", locals: { article: @article } %>
<%= render partial: "article", layout: "box", locals: { article: @article, theme: "dark" } %>

上面的程式碼片段會導致 partial 被編譯兩次,這會花費更多時間並使用更多記憶體。

def _render_template_2323231_article_show(buffer, local_assigns, article:)
  # ...
end

def _render_template_3243454_article_show(buffer, local_assigns, article:, theme:)
  # ...
end

當組合數量較少時,這並不是什麼大問題,但如果數量很大,可能會浪費大量的記憶體並花費很長時間進行編譯。為了對抗這種情況,您可以使用嚴格的本地變數來定義編譯的 partial 簽章,並確保僅編譯 partial 的單一版本。

<%# locals: (article:, theme: "light") -%>
...

您可以使用與 Ruby 方法簽章相同的語法,透過 locals: 簽章來強制執行樣板接受多少個及哪些 locals、設定預設值等等。

以下是一些 locals: 簽章的範例。

<%# app/views/messages/_message.html.erb %>

<%# locals: (message:) -%>
<%= message %>

以上使 message 成為必要的本地變數。如果呈現 partial 時沒有 :message 本地變數引數,將會引發例外狀況。

render "messages/message"
# => ActionView::Template::Error: missing local: :message for app/views/messages/_message.html.erb

如果設定了預設值,則如果未在 locals: 中傳遞 message,則可以使用它。

<%# app/views/messages/_message.html.erb %>

<%# locals: (message: "Hello, world!") -%>
<%= message %>

如果呈現 partial 時沒有 :message 本地變數,則會使用 locals: 簽章中設定的預設值。

render "messages/message"
# => "Hello, world!"

如果呈現 partial 時使用了未在 local: 簽章中指定的本地變數,也會引發例外狀況。

render "messages/message", unknown_local: "will raise"
# => ActionView::Template::Error: unknown local: :unknown_local for app/views/messages/_message.html.erb

您可以使用雙星號 ** 運算子來允許選擇性的本地變數引數。


<%# app/views/messages/_message.html.erb %>

<%# locals: (message: "Hello, world!", **attributes) -%>
<%= tag.p(message, **attributes) %>

或者,您可以將 locals: 設定為空白的 () 來完全停用 locals

<%# app/views/messages/_message.html.erb %>

<%# locals: () %>

如果呈現 partial 時使用任何本地變數引數,則會引發例外狀況。

render "messages/message", unknown_local: "will raise"
# => ActionView::Template::Error: no locals accepted for app/views/messages/_message.html.erb

Action View 將在任何支援以 # 為前綴的註解的樣板引擎中處理 locals: 簽章,並將從 partial 中的任何一行讀取簽章。

僅支援關鍵字引數。定義位置引數或區塊引數將會在呈現時引發 Action View 錯誤。

local_assigns 方法不包含在 local: 簽章中指定的預設值。若要存取具有與保留的 Ruby 關鍵字(例如 classif)相同名稱的預設值的本地變數,可以使用 binding.local_variable_get 來存取這些值。

<%# locals: (class: "message") %>
<div class="<%= binding.local_variable_get(:class) %>">...</div>

5 版面配置

版面配置可用於在 Rails 控制器動作的結果周圍呈現常見的視圖樣板。Rails 應用程式可以有多個版面配置,頁面可以在其中呈現。

例如,應用程式可能有一個用於登入使用者的版面配置,另一個用於網站的行銷部分。登入使用者版面配置可能包含應存在於許多控制器動作中的頂層導覽。SaaS 應用程式的銷售版面配置可能包含諸如「定價」和「聯絡我們」頁面的頂層導覽。不同的版面配置可以有不同的標頭和頁尾內容。

為了找到目前控制器動作的版面配置,Rails 首先會在 app/views/layouts 目錄中尋找與控制器基本名稱相同的文件。例如,從 ProductsController 類別渲染的動作將會使用 app/views/layouts/products.html.erb

如果沒有控制器專用的版面配置,Rails 將會使用 app/views/layouts/application.html.erb

以下是一個 application.html.erb 文件中簡單版面配置的範例

<!DOCTYPE html>
<html>
<head>
  <title><%= "Your Rails App" %></title>
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>
  <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
  <%= javascript_importmap_tags %>
</head>
<body>

<nav>
  <ul>
    <li><%= link_to "Home", root_path %></li>
    <li><%= link_to "Products", products_path %></li>
    <!-- Additional navigation links here -->
  </ul>
</nav>

<%= yield %>

<footer>
  <p>&copy; <%= Date.current.year %> Your Company</p>
</footer>

在上述範例版面配置中,視圖內容將會渲染在 <%= yield %> 的位置,並被相同的 <head><nav><footer> 內容包圍。

Rails 提供了更多方式來為個別的控制器和動作指定特定的版面配置。您可以在 Rails 中的版面配置和渲染 指南中了解更多關於版面配置的資訊。

5.1 部分版面配置

部分視圖可以應用它們自己的版面配置。這些版面配置與應用於控制器動作的不同,但它們的工作方式相似。

假設您在頁面上顯示一篇文章,該文章應該為了顯示目的而被包裹在 div 中。首先,您將建立一個新的 Article

Article.create(body: "Partial Layouts are cool!")

show 模板中,您將渲染包裹在 box 版面配置中的 _article 部分視圖

<%# app/views/articles/show.html.erb %>
<%= render partial: 'article', layout: 'box', locals: { article: @article } %>

box 版面配置僅將 _article 部分視圖包裹在 div

<%# app/views/articles/_box.html.erb %>
<div class="box">
  <%= yield %>
</div>

請注意,部分視圖版面配置可以存取傳遞給 render 呼叫的本地 article 變數,儘管在此案例中,它並未在 _box.html.erb 中使用。

與應用程式範圍的版面配置不同,部分視圖版面配置的名稱仍然具有底線前綴。

您也可以在部分視圖版面配置中渲染程式碼區塊,而不是呼叫 yield。例如,如果您沒有 _article 部分視圖,您可以改為這樣做

<%# app/views/articles/show.html.erb %>
<%= render(layout: 'box', locals: { article: @article }) do %>
  <div>
    <p><%= article.body %></p>
  </div>
<% end %>

假設您使用與上述相同的 _box 部分視圖,這將產生與先前範例相同的輸出。

5.2 具有部分版面配置的集合

在渲染集合時,也可以使用 :layout 選項

<%= render partial: "article", collection: @articles, layout: "special_layout" %>

該版面配置將與集合中每個項目的部分視圖一起渲染。目前的物件和 object_counter 變數,在上述範例中為 articlearticle_counter,也將在版面配置中可用,就像它們在部分視圖中一樣。

6 輔助方法

Rails 提供了許多輔助方法與 Action View 一起使用。這些包括以下方法:

  • 格式化日期、字串和數字
  • 建立 HTML 連結到圖片、影片、樣式表等...
  • 清除內容
  • 建立表單
  • 本地化內容

您可以在 Action View 輔助方法指南Action View 表單輔助方法指南 中了解更多關於輔助方法的資訊。

7 本地化視圖

Action View 能夠根據目前的地區設定渲染不同的模板。

例如,假設您有一個具有 show 動作的 ArticlesController。預設情況下,呼叫此動作將會渲染 app/views/articles/show.html.erb。但是如果您設定 I18n.locale = :de,則 Action View 將首先嘗試渲染模板 app/views/articles/show.de.html.erb。如果沒有本地化的模板,則會使用未裝飾的版本。這表示您不需要為所有情況提供本地化的視圖,但是如果有的話,它們會被優先使用。

您可以使用相同的技術來本地化您 public 目錄中的救援檔案。例如,設定 I18n.locale = :de 並建立 public/500.de.htmlpublic/404.de.html 將允許您擁有本地化的救援頁面。

有關更多詳細資訊,請參閱 Rails 國際化 (I18n) API 文件



返回頂部