1 概觀:各部分如何結合
本指南著重於模型-檢視-控制器三角形中控制器和檢視之間的互動。如您所知,控制器負責協調處理 Rails 中請求的整個流程,儘管它通常會將任何繁重的程式碼交給模型。但是,當需要將回應傳送回使用者時,控制器會將事物交給檢視。而這個交接就是本指南的主題。
廣義上來說,這涉及決定應該將什麼傳送為回應,並呼叫適當的方法來建立該回應。如果回應是一個完整的檢視,Rails 也會執行一些額外的工作,將檢視包覆在一個版面配置中,並可能載入部分檢視。你會在稍後的指南中看到所有這些路徑。
2 建立回應
從控制器角度來看,有 3 種方法可以建立 HTTP 回應
- 呼叫
render
來建立一個完整的回應,傳送回瀏覽器 - 呼叫
redirect_to
來傳送 HTTP 重新導向狀態碼給瀏覽器 - 呼叫
head
來建立一個僅包含 HTTP 標頭的回應,傳送回瀏覽器
2.1 預設呈現:實務中的慣例優先於設定
你聽說過 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
模型,並將索引動作新增到 BooksController
class BooksController < ApplicationController
def index
@books = Book.all
end
end
請注意,根據「慣例優先於設定」原則,我們不會在索引動作的結尾明確呈現。規則是,如果你沒有在控制器動作的結尾明確呈現某個東西,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 從另一個控制器呈現動作的範本
如果您想從與包含動作程式碼的控制器完全不同的控制器中呈現範本,該怎麼辦?您也可以使用接受要呈現範本的完整路徑(相對於 app/views
)的 render
來執行此操作。例如,如果您在 app/controllers/admin
中的 AdminProductsController
中執行程式碼,您可以使用這種方式將動作的結果呈現到 app/views/products
中的範本
render "products/show"
Rails 知道這個檢視屬於不同的控制器,因為字串中嵌入斜線字元。如果您想明確表示,可以使用 :template
選項(Rails 2.2 及更早版本需要使用此選項)
render template: "products/show"
2.2.3 總結
上述兩種呈現方式(呈現同一個控制器中另一個動作的範本,以及呈現不同控制器中另一個動作的範本)實際上是同一個操作的不同變體。
事實上,在 BooksController
類別中,如果書籍未成功更新,我們希望在更新動作中呈現編輯範本,下列所有 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"
當您回應預期收到非正確 HTML 的 Ajax 或網路服務要求時,呈現純文字最為有用。
預設情況下,如果您使用 :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 是許多 Ajax 函式庫使用的 JavaScript 資料格式。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 呈現 Vanilla JavaScript
Rails 可以呈現 Vanilla JavaScript
render js: "alert('Hello Rails');"
這將以 MIME 類型 text/javascript
將提供的字串傳送至瀏覽器。
2.2.10 呈現原始主體
你可以使用 :body
選項來 render
,將原始內容傳送回瀏覽器,而無需設定任何內容類型
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
的物件。
render MyRenderable.new
這會在提供的物件上呼叫 render_in
,並使用目前的檢視內容。
你也可以使用 :renderable
選項來 render
,提供物件
render renderable: MyRenderable.new
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.erb
或 app/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
透過這個宣告,product
版面配置將用於所有內容,但 rss
和 index
方法除外。
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
檢視 - 並擲回錯誤。解決方案很簡單:確保在單一程式碼路徑中只呼叫一次 render
或 redirect
。一個有幫助的方法是 return
。以下是方法的修補版本
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?
的書,並使用 special_show
範本,而其他書將會使用預設的 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_to
和 redirect_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 render
和 redirect_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
要求顯示動作,而控制器發現沒有書籍,因此控制器會傳送 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 將檢視呈現為回應時,會透過將檢視與目前的配置結合,並使用本指南稍早介紹的規則尋找目前的配置。在配置中,您可以使用三個工具,將不同的輸出片段結合,以形成整體回應
- 資源標籤
yield
和content_for
- 部分
3.1 資源標籤輔助工具
資產標籤輔助程式提供用於產生連結檢視至饋送、JavaScript、樣式表、影像、影片和音訊的 HTML 方法。Rails 中有六種可用的資產標籤輔助程式
您可以在配置或其他檢視中使用這些標籤,儘管 auto_discovery_link_tag
、javascript_include_tag
和 stylesheet_link_tag
最常使用於配置的 <head>
區段中。
資產標籤輔助程式不會驗證指定位置中資產的存在;它們僅假設您知道自己在做什麼並產生連結。
3.1.1 使用 auto_discovery_link_tag
連結至饋送
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
標籤。
如果您在啟用 Asset Pipeline 的情況下使用 Rails,這個輔助函式會產生指向 /assets/javascripts/
的連結,而非 Rails 較早版本使用的 public/javascripts
。然後,這個連結會由 Asset Pipeline 提供服務。
Rails 應用程式或 Rails 引擎中的 JavaScript 檔案會放在下列三個位置之一:app/assets
、lib/assets
或 vendor/assets
。這些位置在 Asset Pipeline 指南中的「Asset Organization」區段 中有詳細說明。
如果您比較喜歡,可以指定相對於文件根目錄的完整路徑,或指定 URL。例如,要連結到 app/assets
、lib/assets
或 vendor/assets
之一內名為 javascripts
的目錄中的 JavaScript 檔案,您可以這樣做
<%= javascript_include_tag "main" %>
然後,Rails 會產生類似這樣的 script
標籤
<script src='/assets/main.js'></script>
然後,這個資產的請求會由 Sprockets 寶石提供服務。
要同時包含多個檔案,例如 app/assets/javascripts/main.js
和 app/assets/javascripts/columns.js
<%= javascript_include_tag "main", "columns" %>
要包含 app/assets/javascripts/main.js
和 app/assets/javascripts/photos/columns.js
<%= javascript_include_tag "main", "/photos/columns" %>
要包含 http://example.com/main.js
<%= javascript_include_tag "http://example.com/main.js" %>
3.1.3 使用 stylesheet_link_tag
連結到 CSS 檔案
stylesheet_link_tag
輔助函式會為提供的每個來源傳回一個 HTML <link>
標籤。
如果您在啟用「Asset Pipeline」的情況下使用 Rails,這個輔助函式會產生指向 /assets/stylesheets/
的連結。這個連結會由 Sprockets 寶石處理。樣式表檔案可以儲存在三個位置之一:app/assets
、lib/assets
或 vendor/assets
。
您可以指定相對於文件根目錄的完整路徑,或 URL。例如,要連結到位於 app/assets
、lib/assets
或 vendor/assets
中的 stylesheets
目錄內的樣式表檔案,您可以執行下列動作
<%= stylesheet_link_tag "main" %>
包含 app/assets/stylesheets/main.css
和 app/assets/stylesheets/columns.css
<%= stylesheet_link_tag "main", "columns" %>
包含 app/assets/stylesheets/main.css
和 app/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} %>
您可以提供圖片的替代文字,如果使用者在瀏覽器中關閉圖片,將會使用此文字。如果您沒有明確指定替代文字,它會預設為檔案的名稱,大寫且沒有副檔名。例如,這兩個圖片標籤會傳回相同的程式碼
<%= 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
目錄的相對路徑。此外,您可以指定 size: "#{width}x#{height}"
選項,就像 image_tag
一樣。視訊標籤也可以在最後指定任何 HTML 選項(id
、class
等)。
視訊標籤也支援所有 <video>
HTML 選項,透過 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
,將目前正在呈現的檢視的完整內容插入其中
<html>
<head>
</head>
<body>
<%= yield %>
</body>
</html>
您也可以建立具有多個讓步區域的佈局
<html>
<head>
<%= yield :head %>
</head>
<body>
<%= yield %>
</body>
</html>
檢視的主體將始終呈現到未命名的 yield
。若要將內容呈現到已命名的 yield
,請使用 content_for
方法。
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 或 CSS 檔案載入到一般佈局標頭的標籤。
3.4 使用部分
部分範本(通常稱為「部分」)是將呈現程序分解成更易於管理的區塊的另一種裝置。使用部分,您可以將呈現回應特定部分的程式碼移到其自己的檔案。
3.4.1 命名部分
若要將部分呈現為檢視的一部分,請在檢視中使用 render
方法
<%= render "menu" %>
這將在正在呈現的檢視中,於該點呈現名為 _menu.html.erb
的檔案。請注意開頭底線字元:部分會以開頭底線命名,以將其與一般檢視區分開來,即使它們在參考時不含底線。即使您從另一個資料夾中提取部分,這也仍然成立
<%= render "application/menu" %>
由於檢視部分依賴於與範本和配置相同的 範本繼承,該程式碼將從 app/views/application/_menu.html.erb
中提取部分。
3.4.2 使用部分簡化檢視
使用部分的一種方法是將它們視為子常式的等效項:作為將細節移出檢視的方法,以便您可以更輕鬆地掌握正在發生的事情。例如,您可能有一個看起來像這樣的檢視
<%= 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
部分可以包含應用程式中許多頁面共用的內容。當您專注於特定頁面時,您不需要看到這些部分的詳細資訊。
如本指南的先前部分所述,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 %>
對於應用程式中所有頁面共用的內容,您可以直接從配置使用部分。
3.4.3 部分配置
部分可以使用自己的配置檔案,就像檢視可以使用配置一樣。例如,您可能會這樣呼叫部分
<%= render partial: "link_area", layout: "graybar" %>
這將尋找名為 _link_area.html.erb
的部分,並使用配置 _graybar.html.erb
呈現它。請注意,部分的配置遵循與常規部分相同的開頭底線命名,並放置在與其所屬的部分相同的資料夾中(不在主配置資料夾中)。
另請注意,傳遞其他選項(例如 :layout
)時,必須明確指定 :partial
。
3.4.4 傳遞局部變數
您也可以將局部變數傳遞到部分,讓它們更強大且更靈活。例如,您可以使用此技術來減少新增和編輯頁面之間的重複,同時仍然保留一些不同的內容
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 %>
儘管同一個部分將呈現在兩個檢視中,但 Action View 的提交輔助程式會傳回「建立區域」作為新增動作,並傳回「更新區域」作為編輯動作。
若要在特定情況下將局部變數傳遞到部分,請使用 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 %>
這樣就可以使用部分,而不需要宣告所有局部變數。
每個部分也有一個與部分同名的局部變數(減去開頭底線)。您可以透過 :object
選項將物件傳遞到此局部變數
<%= render partial: "customer", object: @new_customer %>
在 customer
部分中,customer
變數會參考父檢視中的 @new_customer
。
如果您有要呈現在部分中的模型執行個體,可以使用簡寫語法
<%= render @customer %>
假設 @customer
執行個體變數包含 Customer
模型的執行個體,這將使用 _customer.html.erb
呈現在它,並將局部變數 customer
傳遞到部分,它會參考父檢視中的 @customer
執行個體變數。
3.4.5 呈現集合
局部片段在呈現集合時非常有用。當您透過 :collection
選項將集合傳遞給局部片段時,局部片段會針對集合中的每個成員插入一次
index.html.erb
<h1>Products</h1> <%= render partial: "product", collection: @products %>
_product.html.erb
<p>Product Name: <%= product.name %></p>
當局部片段以複數集合呼叫時,局部片段的個別執行個體可以透過以局部片段命名的變數存取正在呈現的集合成員。在這個案例中,局部片段是 _product
,而在 _product
局部片段中,您可以參照 product
以取得正在呈現的執行個體。
這也有簡寫方式。假設 @products
是 Product
執行個體的集合,您可以在 index.html.erb
中簡單撰寫以下內容以產生相同的結果
<h1>Products</h1>
<%= render @products %>
Rails 會透過查看集合中的模型名稱來判斷要使用的局部片段名稱。事實上,您甚至可以建立異質集合並以這種方式呈現,而 Rails 會為集合的每個成員選擇適當的局部片段
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 會根據集合的每個成員適當地使用客戶或員工局部片段。
如果集合為空,render
會傳回 nil,因此提供替代內容應該相當簡單。
<h1>Products</h1>
<%= render(@products) || "There are no products available." %>
3.4.6 本地變數
要在局部片段中使用自訂的本地變數名稱,請在呼叫局部片段時指定 :as
選項
<%= render partial: "product", collection: @products, as: :item %>
透過這個變更,您可以將 @products
集合的執行個體作為局部片段中的 item
本地變數存取。
您也可以使用 locals: {}
選項將任意本地變數傳遞給您正在呈現的任何局部片段
<%= render partial: "product", collection: @products,
as: :item, locals: {title: "Products Page"} %>
在這種情況下,部分會存取名為 title
的區域變數,其值為「產品頁面」。
3.4.7 計數器變數
Rails 也會在部分中提供一個計數器變數,由集合呼叫。變數名稱為部分標題,後接 _counter
。例如,在呈現集合 @products
時,部分 _product.html.erb
可以存取變數 product_counter
。變數會索引部分在封閉檢視中呈現的次數,從第一次呈現的 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
選項
<%= render partial: @products, spacer_template: "product_ruler" %>
Rails 會在每個 _product
部分之間呈現 _product_ruler
部分 (沒有傳入任何資料)。
3.4.9 集合部分配置
在呈現集合時,也可以使用 :layout
選項
<%= render partial: "product", collection: @products, layout: "special_layout" %>
配置會與集合中每個項目的部分一起呈現。目前的物件和 object_counter 變數也會在配置中提供,就像它們在部分中一樣。
3.5 使用巢狀配置
您可能會發現您的應用程式需要一個配置,它與您的常規應用程式配置略有不同,以支援一個特定的控制器。您可以透過使用巢狀配置 (有時稱為子範本) 來達成此目的,而不是重複主配置並編輯它。以下是一個範例
假設您有以下 ApplicationController
配置
app/views/layouts/application.html.erb
<html> <head> <title><%= @page_title or "Page Title" %></title> <%= stylesheet_link_tag "layout" %> <style><%= yield :stylesheets %></style> </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 :stylesheets do %> #top_menu {display: none} #right_menu {float: right; background-color: yellow; color: black} <% 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" %>
這樣就完成了。News 視圖將使用新的版面配置,隱藏頂部選單,並在「內容」div 中新增一個新的右側選單。
使用此技術,有許多方法可以透過不同的子範本配置取得類似的結果。請注意,巢狀層級沒有限制。可以透過 render template: 'layouts/news'
使用 ActionView::render
方法,以 News 版面配置為基礎建立新的版面配置。如果你確定不會對 News
版面配置進行子範本配置,你可以將 content_for?(:news_content) ? yield(:news_content) : yield
替換為 yield
。
回饋
歡迎協助改善本指南的品質。
如果你發現任何錯字或事實錯誤,請協助我們修正。你可以先閱讀我們的 文件貢獻 章節,瞭解如何開始。
你可能會發現內容不完整或過時。請協助新增任何遺漏的 main 文件。請務必先查看 Edge Guides,確認問題是否已在主分支中修正。請查看 Ruby on Rails Guides 指南,瞭解風格和慣例。
如果你發現需要修正的地方,但無法自行修補,請 開啟問題。
最後,歡迎在 Ruby on Rails 官方論壇 討論任何與 Ruby on Rails 文件相關的主題。