[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