更多資訊請見 rubyonrails.org:

Rails 中的版面配置與渲染

本指南涵蓋 Action Controller 和 Action View 的基本版面配置功能。

閱讀本指南後,您將了解

  • 如何使用 Rails 內建的各種渲染方法。
  • 如何建立具有多個內容區段的版面配置。
  • 如何使用局部範本來簡化您的視圖。
  • 如何使用巢狀版面配置(子範本)。

1 概觀:各部分如何組合在一起

本指南著重於模型-視圖-控制器三角形中控制器和視圖之間的互動。如您所知,控制器負責協調處理 Rails 中請求的整個流程,儘管它通常會將任何繁重的程式碼交給模型。但是,當要將回應發送回給使用者時,控制器會將事情交給視圖。本指南的主題就是這種交接。

概括來說,這包括決定應該將什麼作為回應發送,並呼叫適當的方法來建立該回應。如果回應是完整的視圖,Rails 還會執行一些額外的工作,將視圖包裝在版面配置中,並可能引入局部視圖。您將在本指南的稍後部分看到所有這些路徑。

2 建立回應

從控制器的角度來看,有三種建立 HTTP 回應的方法

  • 呼叫 render 來建立完整的回應並傳送回瀏覽器
  • 呼叫 redirect_to 來將 HTTP 重定向狀態碼傳送至瀏覽器
  • 呼叫 head 來建立僅包含 HTTP 標頭的回應並傳送回瀏覽器

2.1 預設渲染:Action 中的慣例優於組態

您聽說過 Rails 推廣「慣例優於組態」。預設渲染就是一個很好的例子。預設情況下,Rails 中的控制器會自動渲染名稱與有效路由對應的視圖。例如,如果您在 BooksController 類別中有以下程式碼

class BooksController < ApplicationController
end

在您的路由檔案中有以下程式碼

resources :books

而且您有一個視圖檔案 app/views/books/index.html.erb

<h1>Books are coming soon!</h1>

當您導覽至 /books 時,Rails 會自動渲染 app/views/books/index.html.erb,並且您會在螢幕上看到「書籍即將推出!」。

但是,即將推出的畫面只具有最低限度的用處,因此您很快就會建立 Book 模型並將 index 動作新增至 BooksController

class BooksController < ApplicationController
  def index
    @books = Book.all
  end
end

請注意,我們沒有按照「慣例優於組態」原則在 index 動作的結尾明確渲染任何內容。規則是,如果您沒有在控制器動作的結尾明確渲染任何內容,Rails 會自動在控制器的視圖路徑中尋找 action_name.html.erb 範本並渲染它。因此,在這種情況下,Rails 會渲染 app/views/books/index.html.erb 檔案。

如果我們想要在視圖中顯示所有書籍的屬性,我們可以透過像這樣的 ERB 範本來實現

<h1>Listing Books</h1>

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Content</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @books.each do |book| %>
      <tr>
        <td><%= book.title %></td>
        <td><%= book.content %></td>
        <td><%= link_to "Show", book %></td>
        <td><%= link_to "Edit", edit_book_path(book) %></td>
        <td><%= link_to "Destroy", book, data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to "New book", new_book_path %>

實際的渲染是由 ActionView::Template::Handlers 模組的巢狀類別完成的。本指南不會深入探討這個過程,但重要的是要知道視圖上的檔案擴充功能會控制範本處理器的選擇。

2.2 使用 render

在大多數情況下,控制器的 render 方法會負責渲染應用程式內容以供瀏覽器使用。有多種方法可以自訂 render 的行為。您可以渲染 Rails 範本的預設視圖、特定範本、檔案、內嵌程式碼或完全不渲染任何內容。您可以渲染文字、JSON 或 XML。您也可以指定渲染回應的內容類型或 HTTP 狀態。

如果您想要查看呼叫 render 的確切結果,而不需要在瀏覽器中檢查它,您可以呼叫 render_to_string。這個方法採用與 render 完全相同的選項,但它會傳回字串,而不是將回應傳送回瀏覽器。

2.2.1 渲染動作的視圖

如果您想要在同一個控制器中渲染對應於不同範本的視圖,您可以使用 render 並指定視圖的名稱

def update
  @book = Book.find(params[:id])
  if @book.update(book_params)
    redirect_to(@book)
  else
    render "edit"
  end
end

如果對 update 的呼叫失敗,則呼叫此控制器中的 update 動作將會渲染屬於同一個控制器的 edit.html.erb 範本。

如果您願意,您可以使用符號而不是字串來指定要渲染的動作

def update
  @book = Book.find(params[:id])
  if @book.update(book_params)
    redirect_to(@book)
  else
    render :edit, status: :unprocessable_entity
  end
end

2.2.2 從另一個控制器渲染動作的範本

如果您想要從完全不同的控制器渲染範本,而不是包含動作程式碼的控制器,該怎麼辦?您也可以使用 render 來執行此操作,它會接受要渲染的範本完整路徑(相對於 app/views)。例如,如果您在 app/controllers/admin 中的 AdminProductsController 執行程式碼,您可以透過這種方式將動作的結果渲染至 app/views/products 中的範本

render "products/show"

Rails 知道此視圖屬於不同的控制器,因為字串中嵌入了斜線字元。如果您想要明確指出,您可以使用 :template 選項(Rails 2.2 和更早版本中需要此選項)

render template: "products/show"

2.2.3 總結

以上兩種渲染方式(渲染同一個控制器中另一個動作的範本,以及渲染不同控制器中另一個動作的範本)實際上是相同操作的變體。

事實上,在 BooksController 類別中,在我們想要在書籍未成功更新時渲染編輯範本的 update 動作內,以下所有 render 呼叫都會在 views/books 目錄中渲染 edit.html.erb 範本

render :edit
render action: :edit
render "edit"
render action: "edit"
render "books/edit"
render template: "books/edit"

您使用哪一種方法實際上是風格和慣例的問題,但經驗法則是使用對您正在撰寫的程式碼最有意義的最簡單方法。

2.2.4 使用 render:inline

如果您願意使用 :inline 選項在方法呼叫中提供 ERB,則 render 方法可以完全不需要視圖。這是完全有效的

render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>"

很少有任何充分的理由使用這個選項。將 ERB 混合到您的控制器中會破壞 Rails 的 MVC 方向,並且會讓其他開發人員更難以理解您專案的邏輯。請改用獨立的 erb 視圖。

預設情況下,內嵌渲染會使用 ERB。您可以使用 :type 選項強制它改用 Builder

render inline: "xml.p {'Horrid coding practice!'}", type: :builder

2.2.5 渲染文字

您可以使用 render 方法的 :plain 選項,將純文字(完全沒有標記)傳回瀏覽器。

render plain: "OK"

當您回應 Ajax 或 Web 服務請求,且預期回傳的內容不是標準的 HTML 時,呈現純文字最為實用。

預設情況下,如果您使用 :plain 選項,則文字會在不使用目前版面的情況下呈現。如果您希望 Rails 將文字放入目前的版面,您需要加入 layout: true 選項,並在版面檔案中使用 .text.erb 擴展名。

2.2.6 呈現 HTML

您可以使用 render 方法的 :html 選項,將 HTML 字串傳回瀏覽器。

render html: helpers.tag.strong("Not Found")

當您要呈現一小段 HTML 程式碼時,這很有用。但是,如果標記很複雜,您可能會考慮將其移至範本檔案。

當使用 html: 選項時,如果字串不是由能識別 html_safe 的 API 組成,HTML 實體將會被跳脫。

2.2.7 呈現 JSON

JSON 是一種 JavaScript 資料格式,許多 Ajax 函式庫都會使用。Rails 內建支援將物件轉換為 JSON 並將該 JSON 呈現回瀏覽器。

render json: @product

您不需要在想要呈現的物件上呼叫 to_json。如果您使用 :json 選項,render 會自動為您呼叫 to_json

2.2.8 呈現 XML

Rails 也內建支援將物件轉換為 XML 並將該 XML 呈現回呼叫者。

render xml: @product

您不需要在想要呈現的物件上呼叫 to_xml。如果您使用 :xml 選項,render 會自動為您呼叫 to_xml

2.2.9 呈現原生 JavaScript

Rails 可以呈現原生 JavaScript。

render js: "alert('Hello Rails');"

這會將提供的字串以 text/javascript 的 MIME 類型傳送到瀏覽器。

2.2.10 呈現原始主體

您可以使用 render 方法的 :body 選項,將原始內容傳回瀏覽器,而不設定任何內容類型。

render body: "raw"

只有在您不關心回應的內容類型時,才應使用此選項。大多數情況下,使用 :plain:html 可能更合適。

除非被覆寫,否則由此呈現選項傳回的回應將會是 text/plain,因為這是 Action Dispatch 回應的預設內容類型。

2.2.11 呈現原始檔案

Rails 可以從絕對路徑呈現原始檔案。這對於有條件地呈現靜態檔案(例如錯誤頁面)很有用。

render file: "#{Rails.root}/public/404.html", layout: false

這會呈現原始檔案(它不支援 ERB 或其他處理程序)。預設情況下,它會在目前版面中呈現。

:file 選項與使用者輸入結合使用可能會導致安全性問題,因為攻擊者可能會使用此動作來存取您檔案系統中具有安全敏感性的檔案。

如果不需要版面配置,send_file 通常是一個更快更好的選擇。

2.2.12 呈現物件

Rails 可以呈現回應 #render_in 的物件。格式可以透過在物件上定義 #format 來控制。

class Greeting
  def render_in(view_context)
    view_context.render html: "Hello, World"
  end

  def format
    :html
  end
end

render Greeting.new
# => "Hello World"

這會使用目前的檢視內容在提供的物件上呼叫 render_in。您也可以使用 render 方法的 :renderable 選項來提供物件。

render renderable: Greeting.new
# => "Hello World"

2.2.13 render 的選項

呼叫 render 方法通常接受六個選項:

  • :content_type
  • :layout
  • :location
  • :status
  • :formats
  • :variants
2.2.13.1 :content_type 選項

預設情況下,Rails 將以 text/html 的 MIME 內容類型提供呈現操作的結果(如果您使用 :json 選項,則為 application/json;對於 :xml 選項,則為 application/xml)。 有時您可能想要更改此設定,您可以透過設定 :content_type 選項來達成。

render template: "feed", content_type: "application/rss"
2.2.13.2 :layout 選項

對於 render 的大多數選項,呈現的內容會顯示為目前版面配置的一部分。您將在本指南後面學習更多關於版面配置以及如何使用它們的知識。

您可以使用 :layout 選項來告訴 Rails 使用特定檔案作為目前動作的版面配置。

render layout: "special_layout"

您也可以告訴 Rails 完全不使用版面配置來呈現。

render layout: false
2.2.13.3 :location 選項

您可以使用 :location 選項來設定 HTTP Location 標頭。

render xml: photo, location: photo_url(photo)
2.2.13.4 :status 選項

Rails 會自動產生具有正確 HTTP 狀態碼的回應(在大多數情況下,這是 200 OK)。 您可以使用 :status 選項來更改此設定。

render status: 500
render status: :forbidden

Rails 理解數字狀態碼和下面顯示的對應符號。

回應類別 HTTP 狀態碼 符號
資訊性 100 :continue
101 :switching_protocols
102 :processing
成功 200 :ok
201 :created
202 :accepted
203 :non_authoritative_information
204 :no_content
205 :reset_content
206 :partial_content
207 :multi_status
208 :already_reported
226 :im_used
重新導向 300 :multiple_choices
301 :moved_permanently
302 :found
303 :see_other
304 :not_modified
305 :use_proxy
307 :temporary_redirect
308 :permanent_redirect
用戶端錯誤 400 :bad_request
401 :unauthorized
402 :payment_required
403 :forbidden
404 :not_found
405 :method_not_allowed
406 :not_acceptable
407 :proxy_authentication_required
408 :request_timeout
409 :conflict
410 :gone
411 :length_required
412 :precondition_failed
413 :payload_too_large
414 :uri_too_long
415 :unsupported_media_type
416 :range_not_satisfiable
417 :expectation_failed
421 :misdirected_request
422 :unprocessable_entity
423 :locked
424 :failed_dependency
426 :upgrade_required
428 :precondition_required
429 :too_many_requests
431 :request_header_fields_too_large
451 :unavailable_for_legal_reasons
伺服器錯誤 500 :internal_server_error
501 :not_implemented
502 :bad_gateway
503 :service_unavailable
504 :gateway_timeout
505 :http_version_not_supported
506 :variant_also_negotiates
507 :insufficient_storage
508 :loop_detected
510 :not_extended
511 :network_authentication_required

如果您嘗試使用非內容狀態碼(100-199、204、205 或 304)呈現內容,它將會從回應中移除。

2.2.13.5 :formats 選項

Rails 使用請求中指定的格式(或預設為 :html)。 您可以使用符號或陣列傳遞 :formats 選項來更改此設定。

render formats: :xml
render formats: [:json, :xml]

如果具有指定格式的範本不存在,則會引發 ActionView::MissingTemplate 錯誤。

2.2.13.6 :variants 選項

這會告訴 Rails 尋找相同格式的範本變化。 您可以使用符號或陣列傳遞 :variants 選項來指定變體的清單。

以下是一個使用範例。

# called in HomeController#index
render variants: [:mobile, :desktop]

使用此組變體,Rails 將會尋找下列一組範本並使用第一個存在的範本。

  • app/views/home/index.html+mobile.erb
  • app/views/home/index.html+desktop.erb
  • app/views/home/index.html.erb

如果具有指定格式的範本不存在,則會引發 ActionView::MissingTemplate 錯誤。

您也可以在控制器動作中的請求物件上設定變體,而不是在 render 呼叫上設定變體。

def index
  request.variant = determine_variant
end

private
  def determine_variant
    variant = nil
    # some code to determine the variant(s) to use
    variant = :mobile if session[:use_mobile]

    variant
  end

2.2.14 尋找版面配置

為了尋找目前的版面配置,Rails 首先會在 app/views/layouts 中尋找與控制器具有相同基本名稱的檔案。 例如,從 PhotosController 類別呈現動作將會使用 app/views/layouts/photos.html.erb(或 app/views/layouts/photos.builder)。 如果沒有此類特定於控制器的版面配置,Rails 將會使用 app/views/layouts/application.html.erbapp/views/layouts/application.builder。 如果沒有 .erb 版面配置,Rails 將會使用 .builder 版面配置(如果存在)。 Rails 也提供了幾種方法,可以更精確地將特定版面配置指派給個別控制器和動作。

2.2.14.1 為控制器指定版面配置

您可以使用 layout 宣告來覆寫控制器中的預設版面配置慣例。 例如:

class ProductsController < ApplicationController
  layout "inventory"
  #...
end

透過此宣告,由 ProductsController 呈現的所有檢視都將使用 app/views/layouts/inventory.html.erb 作為其版面配置。

若要為整個應用程式指派特定版面配置,請在 ApplicationController 類別中使用 layout 宣告:

class ApplicationController < ActionController::Base
  layout "main"
  #...
end

透過此宣告,整個應用程式中的所有檢視都將使用 app/views/layouts/main.html.erb 作為其版面配置。

2.2.14.2 在執行階段選擇版面配置

您可以使用符號將版面配置的選擇延遲到處理請求時:

class ProductsController < ApplicationController
  layout :products_layout

  def show
    @product = Product.find(params[:id])
  end

  private
    def products_layout
      @current_user.special? ? "special" : "products"
    end
end

現在,如果目前使用者是特殊使用者,他們在檢視產品時將會獲得特殊的版面配置。

您甚至可以使用內嵌方法(例如 Proc)來決定版面配置。 例如,如果您傳遞 Proc 物件,您給予 Proc 的區塊將會被給予 controller 實例,因此可以根據目前的請求來決定版面配置:

class ProductsController < ApplicationController
  layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" }
end
2.2.14.3 有條件的版面配置

在控制器層級指定的版面配置支援 :only:except 選項。這些選項採用方法名稱或方法名稱陣列,對應於控制器內的方法名稱:

class ProductsController < ApplicationController
  layout "product", except: [:index, :rss]
end

透過此宣告,除了 rssindex 方法之外的所有項目都會使用 product 版面配置。

2.2.14.4 版面配置繼承

版面配置宣告會在階層中向下層疊,而更特定的版面配置宣告始終會覆寫更一般的宣告。 例如:

  • application_controller.rb

    class ApplicationController < ActionController::Base
      layout "main"
    end
    
  • articles_controller.rb

    class ArticlesController < ApplicationController
    end
    
  • special_articles_controller.rb

    class SpecialArticlesController < ArticlesController
      layout "special"
    end
    
  • old_articles_controller.rb

    class OldArticlesController < SpecialArticlesController
      layout false
    
      def show
        @article = Article.find(params[:id])
      end
    
      def index
        @old_articles = Article.older
        render layout: "old"
      end
      # ...
    end
    

在此應用程式中:

  • 一般而言,檢視將會在 main 版面配置中呈現。
  • ArticlesController#index 將會使用 main 版面配置。
  • SpecialArticlesController#index 將會使用 special 版面配置。
  • OldArticlesController#show 將完全不使用版面配置。
  • OldArticlesController#index 將會使用 old 版面配置。
2.2.14.5 範本繼承

與佈局繼承邏輯類似,如果範本或局部範本在傳統路徑中找不到,控制器將在其繼承鏈中尋找要呈現的範本或局部範本。例如

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
end
# app/controllers/admin_controller.rb
class AdminController < ApplicationController
end
# app/controllers/admin/products_controller.rb
class Admin::ProductsController < AdminController
  def index
  end
end

admin/products#index 動作的查找順序將會是

  • app/views/admin/products/
  • app/views/admin/
  • app/views/application/

這使得 app/views/application/ 成為存放共享局部範本的絕佳位置,這些局部範本可以在您的 ERB 中像這樣呈現

<%# app/views/admin/products/index.html.erb %>
<%= render @products || "empty_list" %>

<%# app/views/application/_empty_list.html.erb %>
There are no items in this list <em>yet</em>.

2.2.15 避免重複渲染錯誤

大多數 Rails 開發人員遲早會看到錯誤訊息「每個動作只能渲染或重新導向一次」。雖然這很煩人,但相對容易修復。通常發生這種情況是因為對 render 的運作方式存在根本的誤解。

例如,以下是一些會觸發此錯誤的程式碼

def show
  @book = Book.find(params[:id])
  if @book.special?
    render action: "special_show"
  end
  render action: "regular_show"
end

如果 @book.special? 的評估結果為 true,Rails 將開始渲染程序,將 @book 變數傾印到 special_show 視圖中。但這將不會停止 show 動作中剩餘程式碼的執行,當 Rails 執行到動作結尾時,它會開始渲染 regular_show 視圖 - 並拋出錯誤。解決方案很簡單:確保在單個程式碼路徑中只有一次呼叫 renderredirectreturn 可以提供幫助。以下是修補過的方法版本

def show
  @book = Book.find(params[:id])
  if @book.special?
    render action: "special_show"
    return
  end
  render action: "regular_show"
end

請注意,ActionController 執行的隱式渲染會偵測是否已呼叫 render,因此以下程式碼可以正常運作,不會出現錯誤

def show
  @book = Book.find(params[:id])
  if @book.special?
    render action: "special_show"
  end
end

這會使用 special_show 範本渲染 special? 設定為 true 的書籍,而其他書籍會使用預設的 show 範本渲染。

2.3 使用 redirect_to

處理對 HTTP 請求返回回應的另一種方法是使用 redirect_to。如您所見,render 告訴 Rails 在建構回應時使用哪個視圖(或其他資源)。redirect_to 方法的功能完全不同:它告訴瀏覽器為不同的 URL 發送新的請求。例如,您可以使用此呼叫,從程式碼中的任何位置重新導向到應用程式中照片的索引頁面

redirect_to photos_url

您可以使用 redirect_back 將使用者返回到他們剛才瀏覽的頁面。此位置是從 HTTP_REFERER 標頭提取的,但無法保證瀏覽器會設定此標頭,因此您必須提供在這種情況下使用的 fallback_location

redirect_back(fallback_location: root_path)

redirect_toredirect_back 不會立即停止並從方法執行中返回,而只是設定 HTTP 回應。方法中在它們之後出現的陳述式將會被執行。如果需要,您可以使用明確的 return 或其他停止機制來停止執行。

2.3.1 取得不同的重新導向狀態碼

當您呼叫 redirect_to 時,Rails 會使用 HTTP 狀態碼 302,即臨時重新導向。如果您想使用不同的狀態碼,例如 301,即永久重新導向,您可以使用 :status 選項

redirect_to photos_path, status: 301

render:status 選項一樣,redirect_to:status 也接受數字和符號標頭指定。

2.3.2 renderredirect_to 之間的差異

有時,經驗不足的開發人員會將 redirect_to 視為一種 goto 命令,將執行從 Rails 程式碼中的一個位置移動到另一個位置。這是正確的。

當前的動作將完成,並向瀏覽器返回回應。在此之後,您的程式碼停止執行並等待新的請求,只是您透過發送回 HTTP 302 狀態碼告訴瀏覽器接下來應該發出什麼請求。

請考慮以下動作以了解差異

def index
  @books = Book.all
end

def show
  @book = Book.find_by(id: params[:id])
  if @book.nil?
    render action: "index"
  end
end

使用此形式的程式碼,如果 @book 變數為 nil,可能會出現問題。請記住,render :action 不會在目標動作中執行任何程式碼,因此不會設定 index 視圖可能需要的 @books 變數。解決這個問題的一種方法是重新導向而不是渲染

def index
  @books = Book.all
end

def show
  @book = Book.find_by(id: params[:id])
  if @book.nil?
    redirect_to action: :index
  end
end

使用此程式碼,瀏覽器會對索引頁面發出新的請求,index 方法中的程式碼將會執行,一切都會正常。

此程式碼的唯一缺點是需要往返瀏覽器:瀏覽器使用 /books/1 請求 show 動作,控制器發現沒有書籍,因此控制器向瀏覽器發送 302 重新導向回應,告訴瀏覽器前往 /books/,瀏覽器遵從並向控制器發送新的請求,現在請求 index 動作,然後控制器取得資料庫中的所有書籍並渲染索引範本,將其發送回瀏覽器,然後在螢幕上顯示。

雖然在小型應用程式中,此額外的延遲可能不是問題,但如果響應時間是一個問題,則需要考慮一下。我們可以使用一個虛構的範例來說明一種處理方法

def index
  @books = Book.all
end

def show
  @book = Book.find_by(id: params[:id])
  if @book.nil?
    @books = Book.all
    flash.now[:alert] = "Your book was not found"
    render "index"
  end
end

這將會偵測到沒有具有指定 ID 的書籍,使用模型中的所有書籍填入 @books 實例變數,然後直接渲染 index.html.erb 範本,將其返回給瀏覽器,並帶有快閃警示訊息,告知使用者發生了什麼事。

2.4 使用 head 來建構僅標頭的回應

可以使用 head 方法向瀏覽器發送僅包含標頭的回應。head 方法接受表示 HTTP 狀態碼的數字或符號(請參閱 參考表)。選項引數被解釋為標頭名稱和值的雜湊。例如,您可以僅返回錯誤標頭

head :bad_request

這會產生以下標頭

HTTP/1.1 400 Bad Request
Connection: close
Date: Sun, 24 Jan 2010 12:15:53 GMT
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
X-Runtime: 0.013483
Set-Cookie: _blog_session=...snip...; path=/; HttpOnly
Cache-Control: no-cache

或者您可以使用其他 HTTP 標頭來傳達其他資訊

head :created, location: photo_path(@photo)

這將會產生

HTTP/1.1 201 Created
Connection: close
Date: Sun, 24 Jan 2010 12:16:44 GMT
Transfer-Encoding: chunked
Location: /photos/1
Content-Type: text/html; charset=utf-8
X-Runtime: 0.083496
Set-Cookie: _blog_session=...snip...; path=/; HttpOnly
Cache-Control: no-cache

3 建構佈局

當 Rails 渲染視圖作為回應時,它會使用本指南前面介紹的尋找目前佈局的規則,將視圖與目前的佈局組合。在佈局中,您可以使用三個工具來組合不同的輸出位元以形成整體回應

3.1 資源標籤輔助程式

資源標籤輔助程式提供用於產生 HTML 的方法,這些 HTML 將視圖連結到饋送、JavaScript、樣式表、圖片、影片和音訊。Rails 中有六個可用的資源標籤輔助程式

您可以在佈局或其他視圖中使用這些標籤,儘管 auto_discovery_link_tagjavascript_include_tagstylesheet_link_tag 最常在佈局的 <head> 區段中使用。

資源標籤輔助程式不會驗證指定位置的資源是否存在;它們只是假設您知道自己在做什麼並產生連結。

auto_discovery_link_tag 輔助程式會建構大多數瀏覽器和饋送閱讀器可以用來偵測是否存在 RSS、Atom 或 JSON 饋送的 HTML。它採用連結的類型(:rss:atom:json)、傳遞給 url_for 的選項雜湊和標籤的選項雜湊

<%= auto_discovery_link_tag(:rss, {action: "feed"},
  {title: "RSS Feed"}) %>

auto_discovery_link_tag 有三個可用的標籤選項

  • :rel 指定連結中的 rel 值。預設值為 "alternate"。
  • :type 指定明確的 MIME 類型。Rails 會自動產生適當的 MIME 類型。
  • :title 指定連結的標題。預設值是大寫的 :type 值,例如 "ATOM" 或 "RSS"。

3.1.2 使用 javascript_include_tag 連結到 JavaScript 檔案

javascript_include_tag 輔助程式會為提供的每個來源傳回 HTML script 標籤。

如果您使用的 Rails 啟用了 資源管道,此輔助程式將產生連結到 /assets/javascripts/ 的連結,而不是早期版本的 Rails 中使用的 public/javascripts。此連結隨後由資源管道提供。

Rails 應用程式或 Rails 引擎中的 JavaScript 檔案位於以下三個位置之一:app/assetslib/assetsvendor/assets。這些位置在 資源管道指南中的資源組織區段 中有詳細說明。

如果您願意,您可以指定相對於文件根目錄的完整路徑或 URL。例如,若要連結到 main.js JavaScript 檔案,該檔案位於 app/assets/javascriptslib/assets/javascriptsvendor/assets/javascripts 其中一個位置內,您可以這樣做

<%= javascript_include_tag "main" %>

然後,Rails 會輸出類似以下的 script 標籤

<script src='/assets/main.js'></script>

然後,此資源的請求由 Sprockets gem 提供。

若要同時包含多個檔案,例如 app/assets/javascripts/main.jsapp/assets/javascripts/columns.js

<%= javascript_include_tag "main", "columns" %>

若要包含 app/assets/javascripts/main.jsapp/assets/javascripts/photos/columns.js

<%= javascript_include_tag "main", "/photos/columns" %>

若要包含 http://example.com/main.js

<%= javascript_include_tag "http://example.com/main.js" %>

stylesheet_link_tag 輔助程式會為提供的每個來源傳回 HTML <link> 標籤。

如果您使用的 Rails 啟用了「資源管道」,則此輔助程式將產生連結到 /assets/stylesheets/ 的連結。此連結隨後由 Sprockets gem 處理。樣式表檔案可以儲存在以下三個位置之一:app/assetslib/assetsvendor/assets

您可以指定相對於文件根目錄的完整路徑或 URL。例如,若要連結到位於 app/assetslib/assetsvendor/assets 其中一個位置的 stylesheets 目錄內的樣式表檔案,您可以這樣做

<%= stylesheet_link_tag "main" %>

若要包含 app/assets/stylesheets/main.cssapp/assets/stylesheets/columns.css

<%= stylesheet_link_tag "main", "columns" %>

若要包含 app/assets/stylesheets/main.cssapp/assets/stylesheets/photos/columns.css

<%= stylesheet_link_tag "main", "photos/columns" %>

若要包含 http://example.com/main.css

<%= stylesheet_link_tag "http://example.com/main.css" %>

預設情況下,stylesheet_link_tag 會使用 rel="stylesheet" 建立連結。您可以透過指定適當的選項 (:rel) 來覆寫此預設值

<%= stylesheet_link_tag "main_print", media: "print" %>

3.1.4 使用 image_tag 連結到圖片

image_tag 輔助程式會建構 HTML <img /> 標籤到指定的檔案。預設情況下,檔案會從 public/images 載入。

請注意,您必須指定圖片的副檔名。

<%= image_tag "header.png" %>

如果您願意,您可以提供圖片的路徑

<%= image_tag "icons/delete.gif" %>

您可以提供額外 HTML 選項的雜湊

<%= image_tag "icons/delete.gif", {height: 45} %>

您可以為圖片提供替代文字,如果使用者在其瀏覽器中關閉圖片,則會使用該文字。如果您未明確指定 alt 文字,則預設為檔案的檔案名稱,以大寫字母開頭且不含副檔名。例如,這兩個圖片標籤會傳回相同的程式碼

<%= image_tag "home.gif" %>
<%= image_tag "home.gif", alt: "Home" %>

您也可以指定特殊的大小標籤,格式為 "{寬度}x{高度}"

<%= image_tag "home.gif", size: "50x20" %>

除了上述特殊標籤外,您還可以提供標準 HTML 選項的最終雜湊,例如 :class:id:name

<%= image_tag "home.gif", alt: "Go Home",
                          id: "HomeImage",
                          class: "nav_bar" %>

3.1.5 使用 video_tag 連結影片

video_tag 輔助方法會建立一個 HTML5 <video> 標籤,指向指定的文件。預設情況下,文件會從 public/videos 目錄載入。

<%= video_tag "movie.ogg" %>

產生

<video src="/videos/movie.ogg" />

就像 image_tag 一樣,您可以提供路徑,可以是絕對路徑,也可以是相對於 public/videos 目錄的路徑。此外,您可以像 image_tag 一樣指定 size: "#{width}x#{height}" 選項。影片標籤也可以在結尾指定任何 HTML 選項(idclass 等)。

影片標籤也透過 HTML 選項雜湊支援所有 <video> HTML 選項,包括

  • poster: "image_name.png",提供一個圖片來取代影片在開始播放前的顯示。
  • autoplay: true,在頁面載入時開始播放影片。
  • loop: true,影片播放到結尾時會循環播放。
  • controls: true,為使用者提供瀏覽器提供的控制項來與影片互動。
  • autobuffer: true,影片會在頁面載入時預先載入檔案。

您也可以透過將影片陣列傳遞給 video_tag 來指定多個要播放的影片。

<%= video_tag ["trailer.ogg", "movie.ogg"] %>

這會產生

<video>
  <source src="/videos/trailer.ogg">
  <source src="/videos/movie.ogg">
</video>

3.1.6 使用 audio_tag 連結音訊檔案

audio_tag 輔助方法會建立一個 HTML5 <audio> 標籤,指向指定的文件。預設情況下,文件會從 public/audios 目錄載入。

<%= audio_tag "music.mp3" %>

您可以根據需要提供音訊文件的路徑。

<%= audio_tag "music/first_song.mp3" %>

您也可以提供額外選項的雜湊,例如 :id:class 等。

video_tag 類似,audio_tag 也有特殊選項

  • autoplay: true,在頁面載入時開始播放音訊。
  • controls: true,為使用者提供瀏覽器提供的控制項來與音訊互動。
  • autobuffer: true,音訊會在頁面載入時預先載入檔案。

3.2 了解 yield

在版面配置的上下文中,yield 會識別應該插入視圖內容的部分。使用此方法最簡單的方式是只有一個 yield,目前正在渲染的視圖的全部內容都會插入到該 yield 中。

<html>
  <head>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

您也可以建立具有多個產生區域的版面配置。

<html>
  <head>
    <%= yield :head %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

視圖的主體將始終渲染到未命名的 yield 中。若要將內容渲染到已命名的 yield 中,請使用與已命名的 yield 相同的引數呼叫 content_for 方法。

新產生的應用程式將在 app/views/layouts/application.html.erb 範本的 <head> 元素中包含 <%= yield :head %>

3.3 使用 content_for 方法

content_for 方法可讓您將內容插入版面配置中已命名的 yield 區塊。例如,這個視圖可以搭配您剛剛看到的版面配置使用。

<% content_for :head do %>
  <title>A simple page</title>
<% end %>

<p>Hello, Rails!</p>

將此頁面渲染到提供的版面配置中的結果將是這個 HTML。

<html>
  <head>
    <title>A simple page</title>
  </head>
  <body>
    <p>Hello, Rails!</p>
  </body>
</html>

當您的版面配置包含不同的區域(例如側邊欄和頁尾)時,content_for 方法非常有用,這些區域應該插入自己的內容區塊。它也可用於將特定頁面的 JavaScript <script> 元素、CSS <link> 元素、特定內容的 <meta> 元素或任何其他元素插入到原本通用的版面配置的 <head> 中。

3.4 使用 Partial

Partial 範本(通常簡稱為「partial」)是將渲染過程分解為更易於管理的小塊的另一種方法。使用 partial,您可以將渲染回應的特定部分的程式碼移到其自己的檔案中。

3.4.1 命名 Partial

若要將 partial 渲染為視圖的一部分,您可以在視圖中使用 render 方法。

<%= render "menu" %>

這將在正在渲染的視圖中的該點渲染名為 _menu.html.erb 的檔案。請注意開頭的底線字元:partial 會使用開頭的底線來命名,以區分它們與一般視圖,即使它們在參照時不使用底線。當您從另一個資料夾提取 partial 時,這也是適用的。

<%= render "application/menu" %>

由於視圖 partial 依賴與範本和版面配置相同的 範本繼承,因此該程式碼將從 app/views/application/_menu.html.erb 提取 partial。

3.4.2 使用 Partial 簡化視圖

使用 partial 的一種方法是將它們視為子程式的等效項:將細節移出視圖,以便您可以更輕鬆地掌握發生的事情。例如,您可能會看到一個看起來像這樣的視圖

<%= render "application/ad_banner" %>

<h1>Products</h1>

<p>Here are a few of our fine products:</p>
<%# ... %>

<%= render "application/footer" %>

在這裡,_ad_banner.html.erb_footer.html.erb partial 可以包含您的應用程式中許多頁面共用的內容。當您專注於特定頁面時,您不需要查看這些部分的詳細資訊。

如本指南的前幾節所示,yield 是清理版面配置的非常強大的工具。請記住,它是純 Ruby,因此您幾乎可以在任何地方使用它。例如,我們可以利用它來簡化幾個類似資源的表單版面配置定義。

  • users/index.html.erb

    <%= render "application/search_filters", search: @q do |form| %>
      <p>
        Name contains: <%= form.text_field :name_contains %>
      </p>
    <% end %>
    
  • roles/index.html.erb

    <%= render "application/search_filters", search: @q do |form| %>
      <p>
        Title contains: <%= form.text_field :title_contains %>
      </p>
    <% end %>
    
  • application/_search_filters.html.erb

    <%= form_with model: search do |form| %>
      <h1>Search form:</h1>
      <fieldset>
        <%= yield form %>
      </fieldset>
      <p>
        <%= form.submit "Search" %>
      </p>
    <% end %>
    

對於應用程式中所有頁面共用的內容,您可以直接從版面配置中使用 partial。

3.4.3 Partial 版面配置

與視圖可以使用版面配置一樣,partial 可以使用其自己的版面配置檔案。例如,您可以像這樣呼叫 partial

<%= render partial: "link_area", layout: "graybar" %>

這將尋找名為 _link_area.html.erb 的 partial,並使用 _graybar.html.erb 版面配置來渲染它。請注意,partial 的版面配置遵循與一般 partial 相同的開頭底線命名,並放置在它們所屬的 partial 的相同資料夾中(而不是在主 layouts 資料夾中)。

另請注意,在傳遞其他選項(例如 :layout)時,必須明確指定 :partial

3.4.4 傳遞區域變數

您也可以將區域變數傳遞到 partial 中,使其更加強大和靈活。例如,您可以使用此技術來減少新頁面和編輯頁面之間的重複,同時仍然保留一些不同的內容。

  • new.html.erb

    <h1>New zone</h1>
    <%= render partial: "form", locals: {zone: @zone} %>
    
  • edit.html.erb

    <h1>Editing zone</h1>
    <%= render partial: "form", locals: {zone: @zone} %>
    
  • _form.html.erb

    <%= form_with model: zone do |form| %>
      <p>
        <b>Zone name</b><br>
        <%= form.text_field :name %>
      </p>
      <p>
        <%= form.submit %>
      </p>
    <% end %>
    

儘管相同的 partial 將渲染到兩個視圖中,但 Action View 的 submit 輔助方法將為 new 動作傳回「Create Zone」,為 edit 動作傳回「Update Zone」。

若要僅在特定情況下將區域變數傳遞給 partial,請使用 local_assigns

  • index.html.erb

    <%= render user.articles %>
    
  • show.html.erb

    <%= render article, full: true %>
    
  • _article.html.erb

    <h2><%= article.title %></h2>
    
    <% if local_assigns[:full] %>
      <%= simple_format article.body %>
    <% else %>
      <%= truncate article.body %>
    <% end %>
    

這樣就可以使用 partial 而無需宣告所有區域變數。

每個 partial 都有一個與 partial 同名的區域變數(減去開頭的底線)。您可以使用 :object 選項將物件傳遞給此區域變數。

<%= render partial: "customer", object: @new_customer %>

customer partial 中,customer 變數將參照父視圖中的 @new_customer

如果您有一個要渲染到 partial 中的模型實例,則可以使用簡寫語法。

<%= render @customer %>

假設 @customer 實例變數包含 Customer 模型的實例,這將使用 _customer.html.erb 來渲染它,並將區域變數 customer 傳遞到 partial 中,該變數將參照父視圖中的 @customer 實例變數。

3.4.5 渲染集合

Partial 在渲染集合時非常有用。當您透過 :collection 選項將集合傳遞給 partial 時,partial 將會為集合中的每個成員插入一次。

  • index.html.erb

    <h1>Products</h1>
    <%= render partial: "product", collection: @products %>
    
  • _product.html.erb

    <p>Product Name: <%= product.name %></p>
    

當使用複數集合呼叫 partial 時,partial 的個別實例可以透過以 partial 命名的變數來存取正在渲染的集合成員。在這種情況下,partial 是 _product,並且在 _product partial 中,您可以參照 product 來取得正在渲染的實例。

這也有一個簡寫形式。假設 @productsProduct 實例的集合,您只需在 index.html.erb 中撰寫此程式碼即可產生相同的結果。

<h1>Products</h1>
<%= render @products %>

Rails 會透過查看集合中的模型名稱來決定要使用的 partial 名稱。事實上,您甚至可以建立異質集合並以這種方式渲染它,Rails 會為集合的每個成員選擇適當的 partial。

  • index.html.erb

    <h1>Contacts</h1>
    <%= render [customer1, employee1, customer2, employee2] %>
    
  • customers/_customer.html.erb

    <p>Customer: <%= customer.name %></p>
    
  • employees/_employee.html.erb

    <p>Employee: <%= employee.name %></p>
    

在這種情況下,Rails 將針對集合的每個成員使用適當的 customer 或 employee partial。

如果集合為空,render 將傳回 nil,因此應該很容易提供替代內容。

<h1>Products</h1>
<%= render(@products) || "There are no products available." %>

3.4.6 區域變數

若要在 partial 中使用自訂區域變數名稱,請在呼叫 partial 時指定 :as 選項。

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

透過此變更,您可以將 @products 集合的實例作為 partial 中的 item 區域變數存取。

您也可以使用 locals: {} 選項將任意區域變數傳遞給您正在渲染的任何 partial。

<%= render partial: "product", collection: @products,
           as: :item, locals: {title: "Products Page"} %>

在這種情況下,partial 將可以存取值為「Products Page」的區域變數 title

3.4.7 計數器變數

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

3.4.8 間隔範本

您也可以透過使用 :spacer_template 選項來指定要主要 partial 的實例之間渲染的第二個 partial。

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

Rails 會在每對 _product partial 之間渲染 _product_ruler partial(沒有傳遞任何資料給它)。

3.4.9 集合 Partial 版面配置

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

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

版面配置將與集合中每個項目的 partial 一起渲染。目前的物件和 object_counter 變數也將在版面配置中可用,就像它們在 partial 中一樣。

3.5 使用巢狀版面配置

您可能會發現您的應用程式需要一個與一般應用程式版面配置略有不同的版面配置,以支援一個特定的控制器。您可以使用巢狀版面配置(有時稱為子範本)來完成此操作,而不是重複主版面配置並進行編輯。以下是一個範例

假設您有以下 ApplicationController 版面配置

  • app/views/layouts/application.html.erb

    <html>
    <head>
      <title><%= @page_title or "Page Title" %></title>
      <%= stylesheet_link_tag "layout" %>
      <%= yield :head %>
    </head>
    <body>
      <div id="top_menu">Top menu items here</div>
      <div id="menu">Menu items here</div>
      <div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div>
    </body>
    </html>
    

在由 NewsController 產生的頁面上,您想要隱藏頂部選單並新增一個右側選單。

  • app/views/layouts/news.html.erb

    <% content_for :head do %>
      <style>
        #top_menu {display: none}
        #right_menu {float: right; background-color: yellow; color: black}
      </style>
    <% end %>
    <% content_for :content do %>
      <div id="right_menu">Right menu items here</div>
      <%= content_for?(:news_content) ? yield(:news_content) : yield %>
    <% end %>
    <%= render template: "layouts/application" %>
    

就是這樣。新聞檢視將會使用新的版面配置,隱藏頂部選單並在 "content" div 內新增一個新的右側選單。

使用此技術,透過不同的子模板方案,有多種方法可以獲得相似的結果。請注意,巢狀層級沒有限制。可以使用 ActionView::render 方法,透過 render template: 'layouts/news' 來基於新聞版面配置建立新的版面配置。如果您確定不會對 News 版面配置進行子模板化,您可以將 content_for?(:news_content) ? yield(:news_content) : yield 替換為簡單的 yield



回到頂端