1 Overview:各個部分如何組合在一起
本指南重點介紹 Model-View-Controller 三角形中 Controller 和 View 之間的中間action。如您所知,Controller 負責協調在 Rails 中處理請求的整個過程,儘管它通常會將任何繁重的程式碼交給 Model。但是,當需要向用戶傳送回應時,Controller 會將事情交給 View。這就是本指南的主題。
概括地說,這涉及決定應該傳送什麼作為回應並呼叫適當的方法來建立該回應。如果回應是一個完整的檢視,Rails 還會做一些額外的工作來將檢視包裝在佈局中,並可能拉入部分 views。您將在本指南後面看到所有這些路徑。
2 建立回應
從 controller 的 view 的角度來看,建立 HTTP 回應的方式有以下三種:
- 呼叫
render
建立一個完整的回應傳送回瀏覽器 - 呼叫
redirect_to
向瀏覽器傳送 HTTP 重定向狀態碼 - 呼叫
head
建立一個僅由 HTTP 標頭組成的回應以傳送回瀏覽器
2.1 預設渲染:Action 中的約定優於設定
您聽說過 Rails 提倡“約定優於設定”。預設渲染就是一個很好的例子。預設情況下,Rails 中的 controllers 會自動使用與有效路由對應的名稱渲染 views。例如,如果您的 BooksController
類中有此程式碼:
class BooksController < ApplicationController
end
以及您的路由檔案中的以下內容:
resources :books
你有一個 view 檔案 app/views/books/index.html.erb
:
<h1>Books are coming soon!</h1>
當您導航到 /books
時,Rails 將自動呈現 app/views/books/index.html.erb
,您將看到“圖書即將推出!”在你的螢幕上。
但是,即將到來的螢幕只有最低限度的用處,因此您將很快建立 Book
model 並將索引 action 新增到 BooksController
:
class BooksController < ApplicationController
def index
@books = Book.all
end
end
請注意,根據“約定優於設定”的原則,我們在索引 action 的末尾沒有顯式渲染。規則是,如果您沒有在 controller action 的末尾顯式渲染某些內容,則 Rails 將自動在 controller 的 view 路徑中查詢 action_name.html.erb
模板並進行渲染。所以在這種情況下,Rails 將渲染 app/views/books/index.html.erb
檔案。
如果我們想顯示 view 中所有書籍的屬性,我們可以使用這樣的 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, method: :delete, data: { confirm: "Are you sure?" } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to "New book", new_book_path %>
實際渲染是由 module ActionView::Template::Handlers
的巢狀類完成的。本指南不會深入研究該過程,但重要的是要知道 view 上的副檔名控制模板處理程式的選擇。
2.2 使用 render
在大多數情況下,ActionController::Base#render
方法會完成渲染應用程式內容以供瀏覽器使用的繁重工作。有多種方式可以自定義render
的行為。您可以為 Rails 模板、特定模板、檔案、內聯程式碼或根本不渲染預設 view。您可以呈現文字、JSON 或 XML。您還可以指定呈現的回應的內容型別或 HTTP 狀態。
提示:如果您想檢視呼叫 render
的確切結果而不需要在瀏覽器中檢查它,您可以呼叫 render_to_string
。此方法採用與 render
完全相同的選項,但它返回一個字串而不是將回應傳送回瀏覽器。
2.2.1 渲染一個 Action 的 View
如果要在同一個 controller 內渲染對應不同模板的 view,可以使用 render
和 view 的名稱:
def update
@book = Book.find(params[:id])
if @book.update(book_params)
redirect_to(@book)
else
render "edit"
end
end
如果呼叫 update
失敗,呼叫這個 controller 中的 update
action 會渲染出屬於同一個 controller 的 edit.html.erb
模板。
如果您願意,可以使用 symbol 而不是字串來指定要呈現的 action:
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 從另一個 Controller 渲染一個 Action 的模板
如果您想從與包含 action 程式碼的模板完全不同的 controller 渲染模板,該怎麼辦?您也可以使用 render
執行此操作,它接受要渲染的模板的完整路徑(相對於 app/views
)。例如,如果您在 app/controllers/admin
中的 AdminProductsController
中執行程式碼,則可以通過以下方式將 action 的結果呈現到 app/views/products
中的模板:
render "products/show"
由於字串中嵌入了斜槓字元,Rails 知道這個 view 屬於不同的 controller。如果你想明確,你可以使用 :template
選項(這是 Rails 2.2 及更早版本所必需的):
render template: "products/show"
2.2.3 收尾
以上兩種渲染方式(在同一個controller渲染另一個action的模板,在不同的controller渲染另一個action的模板)其實是同一個操作的變種。
實際上,在 BooksController 類中,在更新 action 的內部,如果圖書未成功更新,我們希望在其中渲染編輯模板,以下所有渲染呼叫都將渲染 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
方法可以完全沒有 view。這是完全有效的:
render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>"
很少有使用此選項的充分理由。將 ERB 混入您的 controllers 會破壞 Rails 的 MVC 方向,並使其他開發人員更難遵循您的專案邏輯。使用單獨的 erb view 代替。
預設情況下,內聯渲染使用 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
您可以使用 :html
選項將 HTML 字串傳送回瀏覽器
render
:
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 渲染原生 JavaScript
Rails 可以渲染原生 JavaScript:
render js: "alert('Hello Rails');"
這會將提供的字串傳送到具有 text/javascript
的 MIME 型別的瀏覽器。
2.2.10 渲染原始體
您可以將原始內容傳送回瀏覽器,而無需設定任何內容
通過使用 render
的 :body
選項鍵入:
render body: "raw"
提示:僅當您不關心內容型別時才應使用此選項
回應。使用 :plain
或 :html
可能更合適
時間。
除非被覆蓋,否則您從此渲染選項返回的回應將是
text/plain
,因為這是 Action 排程回應的預設內容型別。
2.2.11 渲染原始檔案
Rails 可以從絕對路徑渲染原始檔案。這對 有條件地呈現靜態檔案,如錯誤頁面。
render file: "#{Rails.root}/public/404.html", layout: false
這將呈現原始檔案(它不支援 ERB 或其他處理程式)。經過 預設情況下,它在當前佈局中呈現。
將 :file
選項與使用者輸入結合使用可能會導致安全問題
因為攻擊者可以使用此 action 訪問檔案系統中的安全敏感檔案。
提示:如果不需要佈局,send_file
通常是更快更好的選擇。
2.2.12 渲染物件
Rails 可以渲染物件回應 :render_in
。
render MyRenderable.new
這將使用當前的 view 上下文在提供的物件上呼叫 render_in
。
2.2.13 render
的選項
呼叫 render
方法通常接受六個選項:
:content_type
:layout
:location
:status
:formats
:variants
2.2.13.1 :content_type
選項
預設情況下,Rails 將提供 MIME 內容型別為 text/html
(或 application/json
,如果您使用 :json
選項,或 application/xml
為 :xml
選項。)的渲染操作的結果。有時您可能想要更改此設定,您可以通過設定 :content_type
選項來實現:
render template: "feed", content_type: "application/rss"
2.2.13.2 :layout
選項
使用 render
的大多數選項,渲染的內容顯示為當前佈局的一部分。您將在本指南的後面瞭解有關佈局以及如何使用它們的更多資訊。
您可以使用 :layout
選項告訴 Rails 使用特定檔案作為當前 action 的佈局:
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 理解數字狀態程式碼和如下所示的對應 symbols。
回應類 | HTTP 狀態碼 | Symbol |
---|---|---|
資訊 | 100 | :繼續 |
101 | :switching_protocols | |
102 | :處理 | |
成功 | 200 | :好的 |
201 | :建立 | |
202 | :接受 | |
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 | : 發現 | |
303 | :see_other | |
304 | :not_modified | |
305 | :use_proxy | |
307 | :temporary_redirect | |
308 | :permanent_redirect | |
客戶端錯誤 | 400 | :bad_request |
401 | :未經授權 | |
402 | :payment_required | |
403 | : 禁止 | |
404 | :not_found | |
405 | :method_not_allowed | |
406 | :not_acceptable | |
407 | :proxy_authentication_required | |
408 | :request_timeout | |
第409話:衝突 | ||
第410話: 不見了 | ||
第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話:鎖定 | ||
第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
)。你可以
使用 symbol 或陣列更改此傳遞 :formats
選項:
render formats: :xml
render formats: [:json, :xml]
如果具有指定格式的模板不存在,則會引發 ActionView::MissingTemplate
錯誤。
2.2.13.6 :variants
選項
這告訴 Rails 尋找相同格式的模板變體。
您可以通過使用 symbol 或陣列傳遞 :variants
選項來指定變體列表。
一個使用示例是這樣的。
# 在 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
錯誤。
除了在渲染呼叫上設定變體之外,您還可以在 controller action 中的請求物件上設定它。
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
類渲染 actions 將使用 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 還提供了多種方法來更精確地將特定佈局分配給單獨的 controllers 和 actions。
2.2.14.1 為 Controller 指定佈局
您可以使用 layout
宣告覆蓋 controllers 中的預設佈局約定。例如:
class ProductsController < ApplicationController
layout "inventory"
#...
end
有了這個宣告,由 ProductsController
渲染的所有 views 都將使用 app/views/layouts/inventory.html.erb
作為它們的佈局。
要為整個應用程式分配特定佈局,請在 ApplicationController
類中使用 layout
宣告:
class ApplicationController < ActionController::Base
layout "main"
#...
end
使用此宣告,整個應用程式中的所有 views 都將使用 app/views/layouts/main.html.erb
進行佈局。
2.2.14.2 在執行時選擇佈局
您可以使用 symbol 來推遲佈局的選擇,直到處理請求:
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
現在,如果當前使用者是特殊使用者,那麼他們在 viewing 產品時會得到一個特殊的佈局。
您甚至可以使用內聯方法(例如 Proc)來確定佈局。例如,如果你傳遞一個 Proc 物件,你給 Proc 的塊會被賦予 controller
實例,因此可以根據當前請求確定佈局:
class ProductsController < ApplicationController
layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" }
end
2.2.14.3 條件佈局
在 controller 級別指定的佈局支援 :only
和 :except
選項。這些選項採用方法名稱或方法名稱陣列,對應於 controller 中的方法名稱:
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
在這個應用程式中:
- 一般情況下,views會在
main
佈局中渲染 -
ArticlesController#index
將使用main
佈局 -
SpecialArticlesController#index
將使用special
佈局 -
OldArticlesController#show
將完全不使用佈局 -
OldArticlesController#index
將使用old
佈局
2.2.14.5 模板繼承
與佈局繼承邏輯類似,如果在正常路徑中找不到模板或部分,則 controller 將在其繼承鏈中查詢要呈現的模板或部分。例如:
# 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
action 的查詢順序為:
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 開發人員都會看到錯誤訊息“每個 action 只能渲染或重定向一次”。雖然這很煩人,但修復起來相對容易。通常這是因為對 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
view 中。但這不會阻止 show
action 中的其餘程式碼執行,並且當 Rails 到達 action 的末尾時,它將開始渲染 regular_show
view - 並丟擲錯誤。解決方案很簡單:確保在單個程式碼路徑中只有一次對 render
或 redirect
的呼叫。可以提供幫助的一件事是 and return
。這是該方法的修補版本:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show" and return
end
render action: "regular_show"
end
確保使用 and return
而不是 && return
,因為 && return
由於 Ruby 語言中的運算子優先順序而不起作用。
請注意,由 ActionController 完成的隱式渲染檢測是否已呼叫 render
,因此以下內容將正常工作:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
end
end
這將使用 special_show
模板渲染一本帶有 special?
的書,而其他書將使用預設的 show
模板渲染。
2.3 使用 redirect_to
處理對 HTTP 請求的返回回應的另一種方法是使用 redirect_to
。如您所見,render
告訴 Rails 在構建回應時使用哪個 view(或其他資產)。 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
接受數字和 symbolic 標頭指定。
2.3.2 render
和redirect_to
的差別
有時,缺乏經驗的開發人員會將 redirect_to
視為一種 goto
命令,在您的 Rails 程式碼中將執行從一個位置移動到另一個位置。這是不正確的。您的程式碼停止執行並等待來自瀏覽器的新請求。碰巧您已經通過傳送回 HTTP 302 狀態程式碼告訴瀏覽器它接下來應該發出什麼請求。
考慮這些 actions 以檢視差異:
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
不會在目標 action 中執行任何程式碼,因此不會設定 index
view 可能需要的 @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
方法中的程式碼會執行,一切都會好的。
這段程式碼唯一的缺點是它需要到瀏覽器來回:瀏覽器請求顯示 action 和 /books/1
並且 controller 發現沒有書,所以 controller 向瀏覽器傳送 302 重定向回應,告訴它轉到/books/
,瀏覽器遵守並將新請求傳送回controller,現在要求index
action,然後controller獲取資料庫中的所有書籍並呈現索引模板,將其傳送回瀏覽器,然後顯示它在你的螢幕上。
雖然在小型應用程式中,這種增加的延遲可能不是問題,但如果回應時間是一個問題,則需要考慮。我們可以用一個人為的例子來演示一種處理這個問題的方法:
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 的書籍,將 model 中的所有書籍填充到 @books
實例變數中,然後直接渲染 index.html.erb
模板,將其返回給瀏覽器並帶有 flash 警報訊息,告訴使用者發生了什麼.
2.4 使用 head
構建僅標頭回應
head
方法可用於將僅包含標頭的回應傳送到瀏覽器。 head
方法接受代表 HTTP 狀態程式碼的數字或 symbol(參見 參考表)。 options 引數被解釋為標頭名稱和 values 的雜湊。例如,您可以只返回一個錯誤標頭:
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 將 view 作為回應呈現時,它通過將 view 與當前佈局相結合,使用本指南前面介紹的查詢當前佈局的規則來實現。在佈局中,您可以使用三個工具來組合不同的輸出位以形成整體回應:
- 資產標籤
-
yield
和content_for
- 部分
3.1 資產標籤 Helpers
資產標籤 helpers 提供了產生 HTML 的方法,這些 HTML 將 views 連結到提要、JavaScript、樣式表、影象、視訊和音訊。 Rails 中有六個資產標籤 helpers 可用:
您可以在佈局或其他 views 中使用這些標籤,儘管 auto_discovery_link_tag
、javascript_include_tag
和 stylesheet_link_tag
最常用於佈局的 <head>
部分。
資產標籤helpers做不驗證指定位置資產的存在;他們只是假設您知道自己在做什麼並產生連結。
3.1.1 使用 auto_discovery_link_tag
連結到提要
auto_discovery_link_tag
helper 構建大多數瀏覽器和提要閱讀器可用於檢測 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
value。預設的 value 是“替代”。 -
:type
指定明確的 MIME 型別。 Rails 會自動產生合適的 MIME 型別。 -
:title
指定連結的標題。預設的 value 是大寫的:type
value,例如“ATOM”或“RSS”。
3.1.2 使用 javascript_include_tag
連結到 JavaScript 檔案
javascript_include_tag
helper 為每個提供的源返回一個 HTML script
標籤。
如果您在啟用 Asset Pipeline 的情況下使用 Rails,則此 helper 將產生指向 /assets/javascripts/
的連結,而不是 Rails 早期版本中使用的 public/javascripts
。該連結隨後由 asset pipeline 提供服務。
Rails 應用程式或 Rails 引擎中的 JavaScript 檔案位於以下三個位置之一:app/assets
、lib/assets
或 vendor/assets
。這些位置在Asset Pipeline 指南中的資產組織部分 中有詳細說明。
如果願意,您可以指定相對於文件根目錄的完整路徑或 URL。例如,要連結到位於 app/assets
、lib/assets
或 vendor/assets
之一中名為 javascripts
的目錄中的 JavaScript 檔案,您可以執行以下操作:
<%= javascript_include_tag "main" %>
Rails 然後將輸出一個 script
標籤,例如:
<script src='/assets/main.js'></script>
對該資產的請求隨後由 Sprockets gem 提供服務。
要同時包含多個檔案,例如 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
helper 為每個提供的源返回一個 HTML <link>
標籤。
如果您在啟用“Asset Pipeline”的情況下使用 Rails,則此 helper 將產生指向 /assets/stylesheets/
的連結。該連結隨後由 Sprockets gem 處理。樣式表文件可以儲存在以下三個位置之一: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
helper 將 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" %>
您還可以指定特殊尺寸的標籤,格式為“{width}x{height}”:
<%= 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
helper 將 HTML 5 <video>
標記構建到指定檔案。預設情況下,檔案從 public/videos
載入。
<%= video_tag "movie.ogg" %>
生產
<video src="/videos/movie.ogg" />
與 image_tag
一樣,您可以提供絕對路徑或相對於 public/videos
目錄的路徑。此外,您可以像 image_tag
一樣指定 size: "#{width}x#{height}"
選項。視訊標籤還可以在末尾指定任何 HTML 選項(id
、class
等)。
視訊標籤還通過 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
helper 將 HTML 5 <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
標識應插入來自 view 的內容的部分。最簡單的使用方法是有一個單獨的 yield
,當前正在渲染的 view 的全部內容都插入其中:
<html>
<head>
</head>
<body>
<%= yield %>
</body>
</html>
您還可以建立具有多個屈服區域的佈局:
<html>
<head>
<%= yield :head %>
</head>
<body>
<%= yield %>
</body>
</html>
view 的主體將始終渲染為未命名的 yield
。要將內容呈現到命名的 yield
中,請使用 content_for
方法。
3.3 使用 content_for
方法
content_for
方法允許您將內容插入到佈局中命名的 yield
塊中。例如,這個 view 將適用於您剛剛看到的佈局:
<% 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 命名部分
要將部分渲染為 view 的一部分,請使用 view 中的 render
方法:
<%= render "menu" %>
這將在被渲染的檢視中的那個點渲染一個名為 _menu.html.erb
的檔案。注意前導下劃線字元:partials 用前導下劃線命名,以差別於正常的 views,即使它們被引用時沒有下劃線。即使您從另一個資料夾中提取部分內容也是如此:
<%= render "shared/menu" %>
該程式碼將從 app/views/shared/_menu.html.erb
中提取部分內容。
3.4.2 使用 Partials 來簡化 Views
使用部分的一種方法是將它們視為子例程的等價物:作為一種將細節移出 view 的方法,以便您可以更輕鬆地掌握正在發生的事情。例如,您可能有一個如下所示的 view:
<%= render "shared/ad_banner" %>
<h1>Products</h1>
<p>Here are a few of our fine products:</p>
...
<%= render "shared/footer" %>
在這裡,_ad_banner.html.erb
和 _footer.html.erb
部分可以包含
由應用程式中的許多頁面共享的內容。你不需要看
當您專注於特定頁面時,這些部分的詳細資訊。
正如本指南前面的部分所見,yield
是一個非常強大的工具
用於清理您的佈局。請記住,它是純 Ruby,因此您可以使用
它幾乎無處不在。例如,我們可以使用它來乾燥表單佈局
幾個類似資源的定義:
-
users/index.html.erb
<%= render "shared/search_filters", search: @q do |form| %> <p> Name contains: <%= form.text_field :name_contains %> </p> <% end %>
-
roles/index.html.erb
<%= render "shared/search_filters", search: @q do |form| %> <p> Title contains: <%= form.text_field :title_contains %> </p> <% end %>
-
shared/_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 區域性佈局
區域性可以使用自己的佈局檔案,就像 view 可以使用佈局一樣。例如,您可以像這樣呼叫部分:
<%= render partial: "link_area", layout: "graybar" %>
這將查詢名為 _link_area.html.erb
的部分並使用佈局 _graybar.html.erb
呈現它。請注意,分音的佈局遵循與正常分音相同的前導下劃線命名,並與它們所屬的分音放在同一資料夾中(不在主 layouts
資料夾中)。
另請注意,在傳遞其他選項(如 :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 %>
儘管相同的部分會被渲染到 views 中,但 Action View 的提交 helper 將為新的 action 返回“建立區域”和為編輯 action 返回“更新區域”。
要僅在特定情況下將區域性變數傳遞給區域性變數,請使用 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
變數將引用來自父 view 的 @new_customer
。
如果您有一個 model 的實例要渲染為部分,則可以使用速記語法:
<%= render @customer %>
假設 @customer
實例變數包含 Customer
model 的實例,這將使用 _customer.html.erb
來呈現它,並將區域性變數 customer
傳遞到將引用父 ZWTHZ21 中的 @customer
實例變數的部分。
3.4.5 渲染集合
Partials 在渲染集合時非常有用。當您通過 :collection
選項將集合傳遞給區域性時,將為集合中的每個成員插入一次區域性:
-
index.html.erb
<h1>Products</h1> <%= render partial: "product", collection: @products %>
-
_product.html.erb
<p>Product Name: <%= product.name %></p>
當使用複數集合呼叫區域性時,區域性的各個實例可以通過以區域性命名的變數訪問正在呈現的集合成員。本例中partial為_product
,在_product
區域性內,可以參考product
獲取正在渲染的實例。
這也有一個簡寫。假設 @products
是 Product
實例的集合,您可以簡單地將其寫入 index.html.erb
以產生相同的結果:
<h1>Products</h1>
<%= render @products %>
Rails 通過檢視集合中的 model 名稱來確定要使用的部分名稱。事實上,你甚至可以建立一個異構集合並以這種方式渲染它,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"} %>
在這種情況下,部分將可以使用 value“產品頁面”訪問區域性變數 title
。
提示:Rails 還在集合呼叫的部分中提供了一個計數器變數,以部分的標題命名,後跟 _counter
。例如,當渲染一個集合 @products
時,部分 _product.html.erb
可以訪問變數 product_counter
,它索引它在封閉的 view 內渲染的次數。請注意,它也適用於使用 as:
選項更改部分名稱的情況。例如,上面程式碼的計數器變數是 item_counter
。
您還可以使用 :spacer_template
選項指定要在主要部分的實例之間呈現的第二部分:
3.4.7 墊片模板
<%= render partial: @products, spacer_template: "product_ruler" %>
Rails 將渲染每對 _product
部分之間的 _product_ruler
部分(沒有傳入資料)。
3.4.8 集合部分佈局
渲染集合時,也可以使用 :layout
選項:
<%= render partial: "product", collection: @products, layout: "special_layout" %>
佈局將與集合中每個專案的部分一起呈現。當前物件和 object_counter 變數也將在佈局中可用,就像它們在部分中一樣。
3.5 使用巢狀佈局
您可能會發現您的應用程式需要一種與正常應用程式佈局略有不同的佈局來支援一個特定的 controller。您可以通過使用巢狀佈局(有時稱為子模板)來完成此操作,而不是重複主佈局並對其進行編輯。下面是一個例子:
假設您有以下 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" %>
而已。新聞 views 將使用新的佈局,隱藏頂部選單並在“內容”div 內新增一個新的右側選單。
有幾種方法可以使用這種技術通過不同的子模板方案獲得類似的結果。請注意,巢狀級別沒有限制。可以通過 render template: 'layouts/news'
使用 ActionView::render
方法在新聞佈局的基礎上建立新佈局。如果您確定不會對 News
佈局進行子模板化,則可以簡單地將 content_for?(:news_content) ? yield(:news_content) : yield
替換為 yield
。
回饋
我們鼓勵您幫助提高本指南的品質。
如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。
您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。
如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue。
關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區。