[Rails] Action View, Layout, Partial, Render
Action View Overview @ RailsGuides Layouts and Rendering in Rails @ RailsGuides
[TOC]
速記
# 當 yield 後沒有接任何參數時,
# 它會自動轉譯該 controller#action 下的
# views/controller_name/action_name.html.erb 樣版(template)
<%= yield %>
概念
在 Rails 中,web requests 是透過 Action Controller 和 Action Viewer 來處理,一般來說 Action Controller 關注的是和資料庫溝通以及處理 CRUD;Action View 則負責編譯要回傳的 response。
從 Controller 中,我們主要有三種建立回應的方式,分別是 render
, redirect_to
和 head
:
render
: 建立一個完整的回應,並回傳給瀏覽器。redirect_to
:傳送 HTTP redirect status code 給瀏覽器。head
:傳送只有 HTTP headers 的內容給瀏覽器。
在 Rails 中,一份完整的 HTML 檔會包含 templates
, partials
和 layouts
三個部分。
Templates
一般來說,我們不會在 View 中去使用 puts
或 print
來輸出結果,而是直接使用 <%= %>
。
慣例:在
articles_controller.rb
中的 index action (articles#index
)會使用在app/views/articles
中的index.html.erb
這支 Template。
Partials
keywords: render
, collection
Partial Templates 一般會簡稱為 partials,透過 partials,可以把重複的 code 拉出來,在其他的 templates 中重複使用它。
慣例:partials 命名的慣例會在檔名前面加上底線
_
,以此來區別是一般的 templates 或是 partials 。
render 的使用(in Views)
使用 render
關鍵字可以載入 partials:
# 會去轉譯同資料夾下的 _menu.html.erb 這支檔案
<%= render "menu" %>
# 會去轉譯 app/views/shared/_menu.html.erb 這支檔案
<%= render "shared/menu" %>
render 可以接受兩個參數,partials
和 locals
,在預設的情況下,會以和該 partials 同名的方式來命名 partial 中的 local variable。
透過 locals
我們可以在 _product.html.erb
這個 partial 中使用 product
這個 local variable,值則是 @product
:
<%= render partial: "product", locals: { product: @product } %>
# 縮寫
<%= render "product", product: @product %>
# 再縮
<%= render partial: "product" %>
options
object
:可以在 partial 的檔案中透過product
這個 local_variable 取得@item
:
<%= render partial: 'product', object: @item %>
as
:搭配object
使用,可以在 partial 的檔案中透過item
這個 local_variable 取得@item
:
<%= render partial: 'product', object: @item , as: 'item'%>
# 等同於
<%= render partial: "product", locals: { item: @item } %>
使用 collection 疊代陣列
有時候我們需要將陣列的資料以疊代的方式呈現在 HTML 上,這時候如果疊代的資料要套用 partial 的話,可以使用 collection
關鍵字,如此將可以在 partial 中使用到該 collection (@products
)的陣列元素(product
),partial 中陣列元素 local variable 的命名是使用該 partial 名稱:
# 原本的寫法
<% @products.each do |product| %>
<%= render partial: "product", locals: { product: product } %>
<% end %>
# 使用 collection 關鍵字
<%= render partial: "product", collection: @products %>
# 縮寫
<%= render @products %>
render 的使用 (in Controller)
render :Symbol
render template: 'index' # 等同於 render 'index', 也等同於 render :index
render plain: "OK"
render html: "<strong>Not Found</strong>".html_safe
# 不用另外呼叫 to_json, to_xml,Rails 會自動處理
render json: @product
render xml: @product
# 以 MIME type of text/javascript 回傳瀏覽器
render js: "alert('Hello Rails');"
在 Rails 中不斷強調慣例優於設定,因此如果你沒有明確的在 controller 的 action 中定義要 render
的內容,Rails 會自動尋找並轉譯名為 app/views/controller_name/action_name.html.erb
的 template:
# 會自動轉譯 `app/views/books/index.html.erb`
class BooksController < ApplicationController
def index
# render 'index' # 可以省略會自動轉譯
end
end
轉譯同一個 controller 內的不同 template
render [String || Symbol]
class BooksController < ApplicationController
def index
render :index # 等同於 render 'index'
end
end
轉譯不同 controller 中的 template
如果你是在 app/controllers/admin
中的 AdminProductsController
你可以透過 render "products/show"
來轉譯 app/views/products
:
# 在 app/controllers/admin 的 controller 中轉譯 app/views/products
class AdminProductsController < ApplicationController
def index
render 'products/show'
# 等同於 render template: "products/show"
end
end
其他相同結果但不同寫法
wrapping it up @ RailsGuides
options for render
:content_type
:改變 content_type 格式,預設是text/html
。:layout
:location
:設定 HTTP Location header:status
:Rails 預設會自動產生適當的 status code,可以透過這個指定 status code。:formats
:預設是 html
# content_type:
render file: filename, content_type: "application/rss"
# :layout:
render layout: "special_layout"
render layout: false
# location:
render xml: photo, location: photo_url(photo)
# status:
render status: 500
render status: :forbidden
# formats:
render formats: :xml
render formats: [:json, :xml]
Semantic Status Code @ RailsGuides
respond_to 的使用(in Controller)
在 Rails 中當我們寫,會自動代入 render 'index'
的指令,而且預設可以回傳任何形態的檔案:
def index
@posts = Post.all
end
所以當網址是使用不同的副檔名時,都會回傳相對應的內下:
http://localhost:3000/posts # 等同於 http://localhost:3000/posts.html
http://localhost:3000/posts.js
http://localhost:3000/posts.json
但也可以透過 respond_to
指定只能回傳的檔案類型,這裡則是可以回傳 html
和 json
格式的檔案,當我們去請求其他的檔案型態時,都會回傳錯誤:
respond_to :html, :json
至於要回傳哪個資料型態回去,取決於 client 所傳送的 HTTP Accept Header 中希望的資料型態為何:
def create
@post = Post.new(post_params)
respond_to do |format|
if @post.save
format.html { redirect_to @post, notice: 'Post was successfully created.' }
format.json { render :show, status: :created, location: @post }
else
format.html { render :new }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
respond_with 的使用
透過 respond_to
可以設定要回傳的資料型態,但如果在每個 action 中都要寫一次的話有點麻煩,因此可以使用 respond_with
這個方法,可以先告知 controller respond_to 支援哪幾種檔案形態,如此一來在 action 內只要寫 respond_with 就可以了:
respond_to :html, :xml, :json
def index
@topics = Topic.all
respond_with(@topics)
end
Layout
Rails 會先去 app/views/layouts
中和 controller 相同名稱的 layout,例如 PhotosController 會使用 app/views/layouts/photos.html.erb
,如果 layout 不存在,則會使用 app/views/layouts/application.html.erb
。
如果想要變更預設值,可以使用:
# Products Controller 中的 View 會以 app/views/layouts/inventory.html.erb 作為 layout
class ProductsController < ApplicationController
layout "inventory"
end
# 整個 Controller 會使用同一個 Layout,app/views/layouts/main.html.erb
class ApplicationController < ActionController::Base
layout "main"
end