跳至主要内容

[Rails] Action Controller Overview

keywords: params, permit, fetch, require, queryString

在 Rails 中,當我們使用 Scaffold 時,會自動產生幾個 controller,像是 index, show, new, edit, create, updatedestroy,這幾個是 Rails 中的保留字。

Action Controller Overview @ Rails Guides

Parameters

在網頁應用程式中通常有兩種獲取參數(parameters)的方式:

  • 一種是 query string parameters,這通常是透過 GET 方法帶在 URL 的 ? 後;
  • 一種是 POST data,通常是透過 POST 方法傳送到後端。

在 Rails 中並沒有區分這兩種 parameters ,他們都可以在 controller 中透過 params 這個 hash 取得。params 這個物件就和 Hash 沒什麼差別,但是可以讓你透過 SymbolString 都可以當作鍵(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_nameaction_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

參考資料