[Rails] Action Controller Overview
keywords: params
, permit
, fetch
, require
, queryString
在 Rails 中,當我們使用 Scaffold 時,會自動產生幾個 controller,像是 index
, show
, new
, edit
, create
, update
和 destroy
,這幾個是 Rails 中的保留字。
Action Controller Overview @ Rails Guides
Parameters
在網頁應用程式中通常有兩種獲取參數(parameters)的方式:
- 一種是
query string parameters
,這通常是透過GET
方法帶在 URL 的?
後; - 一種是
POST data
,通常是透過POST
方法傳送到後端。
在 Rails 中並沒有區分這兩種 parameters ,他們都可以在 controller 中透過 params
這個 hash 取得。params
這個物件就和 Hash
沒什麼差別,但是可以讓你透過 Symbol
或 String
都可以當作鍵(Key)。
Hash and Array Parameters
Array parameters
如果我們需要傳送陣列資料,可以透過這樣的方式傳送:
GET /clients?ids[]=1&ids[]=2&ids[]=3
在 Controller 中會收到 params[:ids]
等同於 ["1", "2", "3"]
。
注意:parameter 的值總是會是字串,因此 Rails 不需要去猜測它的資料型別。
如果傳送的參數是 [nil]
或 [nil, nil, ...]
那麼基於安全的理由,預設會全部替代為 []
。
Hash parameters
如果要傳送 Hash 的資料,則資料傳送可以像這樣:
<form accept-charset="UTF-8" action="/clients" method="post">
<input type="text" name="client[name]" value="Acme" />
<input type="text" name="client[phone]" value="12345" />
<input type="text" name="client[address][postcode]" value="12345" />
<input type="text" name="client[address][city]" value="Carrot City" />
</form>
如此將會收到 params[:client]
,其中內容會是:
{
"name" => "Acme",
"phone" => "12345",
"address" => { "postcode" => "12345", "city" => "Carrot City" }
}
可以注意到 params[:client][:address]
會是 hash 裡面又包著 hash
JSON Parameters
如果資料傳送的 Content-Type
設成是 application/json
的話,Rails 會自動將資料存到 params
這個 hash 中。因此,如果傳送的資料是:
// JSON
{ "company": { "name": "acme", "address": "123 Carrot Street" } }
會得到 params[:company]
且值為 { "name" => "acme", "address" => "123 Carrot Street" }
。
Routing Parameters
keywords: controller_name
, action_name
params
這個 hash 總是會包含 :controller
和 :action
這兩個 key,但你還是應該使用 controller_name
和 action_name
這兩個方法,而不是直接使用該 key 取值。
其他定義在路由中的參數像是 :id
也可以透過相同的方法取得,假設我們在路由中設定個參數叫做 :status
:
get '/clients/:status' => 'clients#index', foo: 'bar'
當使用者進入 /clients/active
的 URL 時,params[:status]
就會被設成 "active"
,而且當這個路由被使用時, params[:foo]
的值就會是 "bar"
(就像透過 query string
設定一樣)。
在 params
中,params[:action]
會是 "index"
,而 params[:controller]
會是 "clients"
。
Strong Parameters
keywords: strong parameters
, mass assignments
, require
, permit
WhiteList parameters
params.require 或 params.fetch
一般來說為了避免不安全的參數進入後端,如果 Action Controller 中所拿到的參數沒有加入白名單前,都是禁止進入 Active Model
的。因此要傳入 Model 的參數都會先經過處理,通常會放在 controller 的 private
中:
class PeopleController < ActionController::Base
# 由於 params[:person] 這個參數並未被允許,
# 因此會噴 ActiveModel::ForbiddenAttributesError 的錯誤
def create
Person.create(params[:person])
end
# 由於在 private method 中已經定義了 person_params 是可被允許的參數
# 因此可以成功 update! 但若 params[:person] 不存在時(使用 params.require),
# 會因為缺乏參數而噴 ActionController::ParameterMissing 的錯誤
def update
person = current_account.people.find(params[:person])
person.update!(person_params)
redirect_to person
end
private
# 使用 private method 來封裝可允許的參數
# require(:person) 表示可以允許 person 物件
# .permit(:name, :age) 表示允許 person 物件裡面的 :name 和 :age 物件
def person_params
params.require(:person).permit(:name, :age)
# params.fetch(:product, {}).permit(:title, :description)
end
end
require 和 fetch 的主要差異在
- 當 require 的 Hash 如果不存在時,會直接噴錯(ActionController::ParameterMissing: param is missing or the value is empty: product)
- 當 fetch 的 Hash 不存在時,不會直接噴錯,而是會得到空的 Hash
傳進來的資料格式長這樣:
{
"id": 1,
"person": {
"name": "Aaron",
"age": 29
}
}
Permitted Scalar Values
如果只想允許某些單一參數,可以使用:
params.permit(:id)
如果想要允許單一參數,並且限制型別:
params.permit(id: [])
Session and Flash
Action Controller Overview: Session @ RailsGuides
Session
在 Rails 中 Session 可以像 Hash 一樣被使用,取用 session 的內容,只需使用 session[:session_key]
:
# 取用 session 內容
def current_user
@_current_user ||= session[:current_user_id] &&
User.find_by(id: session[:current_user_id])
end
使用 session[:session_key]
即可儲存 session:
# 儲存 session 內容
def create
if user = User.authenticate(params[:username], params[:password])
session[:current_user_id] = user.id
redirect_to root_url
end
end
若要移除 session 內的內容,只需將它設成 nil
即可:
def destroy
# Remove the user id from the session
@_current_user = session[:current_user_id] = nil
redirect_to root_url
end
Flash
FlashHash @ Rails API
flash 是一種特別形式的 session,它的值只會在下一次的 request 中可以被使用,因此經常會用來處理錯誤訊息。
flash 的使用方式和 session 一樣,可以作為 Hash 使用:
# foo_controllers.rb
# flash 的內容會在下一次 request 中可以被使用
def destroy
session[:current_user_id] = nil
flash[:notice] = "You have successfully logged out."
# flash.now[:error] = "Could not save client" # 不要在下次 request 才拿到
redirect_to root_url
end
除了使用預設的 flash_key 像是 notice
, alert
,也可以自訂 flash 的內容:
# foo_controllers.eb
# 使用預設的 flash
redirect_to root_url, notice: "You have successfully logged out."
redirect_to root_url, alert: "You're stuck here!"
# 自訂 flash
redirect_to root_url, flash: { just_signed_up: 'Welcome', referral_code: 1234 }
在下一次的 request 中(除非使用 flash.now[]
),即可在 View 中使用到此 flash:
<html>
<!-- <head/> -->
<body>
<!-- 列出所有 flash 的內容 -->
<% flash.each do |name, msg| -%>
<%= content_tag :div, msg, class: name %>
<% end -%>
<!-- more content -->
</body>
</html>
使用 flash 預設的 notice
, alter
:
<% if notice %>
<%= render 'shared/flash.html' %>
<% end %>
<% if alter %>
<%= render 'shared/flash.html' %>
<% end %>
或者呼叫特定 key 的 flash 內容:
<% if flash[:just_signed_up] %>
<p class="welcome">Welcome to our site!</p>
<% end %>
Generator
# 新增 pages controller
bin/rails g controller pages
將內容直接輸出到網頁
class PagesController < ApplicationController
def hello
puts "Puts 的內容會出現在 Rails 的 console 中,可以在 Terminal 看到"
# render plain: "<h1>你好,世界!</h1>"
# render html: "<h1>你好,世界!</h1>".html_safe
# render json: object.to_json
# render template: 'pages/index.html.erb', layout: 'application'
end
end
參考資料
- Action Controller Overview @ Rails Guides
- Treehouse
- 為你自己學 Ruby on Rails
- Layout and Rendering @ Rails Guide