概要
railsではstrong parametersを用いて, 許可されていないパラメータによってモデルの重要な属性が誤って更新されてしまうことを防止することができます
パラメータの許可にはpermit
を用います
また必須パラメータが不足している場合, require
やfetch
を使うことで例外発生やデフォルトの値を設定することができます
さらにrails8からはパラメータの必須化と許可を同時に行うexpect
が導入されました
この記事ではpermit
, require
とfetch
ついて確認し, rails8で導入されたexpect
について見ていきます
permitについて
permit
を用いることで許可するパラメータを決めることができます
スカラー, 配列, ハッシュなどで許可することができます
例として下記のようなparams
を用います
ActionController::Parameters.new
で初期化したオブジェクトのpermited
はfalse
となっています
params = ActionController::Parameters.new({name: "name", numbers: [3, 5, 7], animal_hash: {dog: "wan", cat: "nya"}})
=> #<ActionController::Parameters {"name"=>"name", "numbers"=>[3, 5, 7], "animal_hash"=>{"dog"=>"wan", "cat"=>"nya"}} permitted: false>
スカラー
キーを指定することで取得できます
permited = params.permit(:name)
=> #<ActionController::Parameters {"name"=>"name"} permitted: true>
permited[:name]
=> "name"
ハッシュの中のスカラーだけを取得することは出来ません
permited = params.permit(:dog)
=> #<ActionController::Parameters {} permitted: true>
permited.has_key?(:dog)
=> false
配列
キーを指定するだけでは取得出来ません
permited = params.permit(:numbers)
=> #<ActionController::Parameters {} permitted: true>
permited.has_key?(:numbers)
=> false
空配列を対応付ける必要があります
permited = params.permit(numbers: [])
=> #<ActionController::Parameters {"numbers"=>[3, 5, 7]} permitted: true>
permited.has_key?(:numbers)
=> true
permited[:numbers]
=> [3, 5, 7]
ハッシュ
ハッシュはキーを指定するだけ, また空配列を対応付けることで取得することはできません
permited = params.permit(:animal_hash)
=> #<ActionController::Parameters {} permitted: true>
permited.has_key?(:animal_hash)
=> false
permited = params.permit(animal_hash: [])
=> #<ActionController::Parameters {} permitted: true>
permited.has_key?(:animal_hash)
=> false
ハッシュで取得するときは空のハッシュを割り当てる必要があります
ハッシュで取得するときはActionController::Parameters
のオブジェクトのままです
permited = params.permit(animal_hash: {})
=> #<ActionController::Parameters {"animal_hash"=>#<ActionController::Parameters {"dog"=>"wan", "cat"=>"nya"} permitted: true>} permitted: true>
permited.has_key?(:animal_hash)
=> true
permited[:animal_hash]
=> #<ActionController::Parameters {"dog"=>"wan", "cat"=>"nya"} permitted: true>
permited[:animal_hash].class
=> ActionController::Parameters
permited[:animal_hash][:dog]
=> "wan"
requireについて
require
で指定されたキーがない場合には例外が発生します
またpermit
を実行しないと, permitted
はfalse
のままになります
つまりrequire
ではパラメータの許可は出来ません
さらにrequire
で指定されたものはキーとして取得できなくなります
例として下記のparams
を用います
params = ActionController::Parameters.new({ article: { title: "title", content: "content" }})
=> #<ActionController::Parameters {"article"=>{"title"=>"title", "content"=>"content"}} permitted: false>
"title"
と"content"
を取得するためにはrequire
に:article
, permit
に:title, :content
を渡します
permited = params.require(:article).permit(:title, :content)
=> #<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: true>
require
のキーで指定した:article
は取得することができなくなります
permited[:article]
=> nil
:title
と:content
は取得できます
permited[:title]
=> "title"
permited[:content]
=> "content"
require
で指定されたキー(:author
)がパラメータにない場合, 例外が発生します
permited = params.require(:author).permit(:name)
param is missing or the value is empty or invalid: author (ActionController::ParameterMissing)
またrequire
だけではpermitted
はfalse
のままなので, パラメータを許可するためにはpermit
を実行する必要があります
not_permitted = params.require(:article)
=> #<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: false>
not_permitted.permitted?
=> false
複数のキー(:article, :author
)で構成されているものを考えます
params = ActionController::Parameters.new({ article: { title: "title", content: "content" }, author: { name: "name"} })
=> #<ActionController::Parameters {"article"=>{"title"=>"title", "content"=>"content"}, "author"=>{"name"=>"name"}} permitted: false>
1つのキー(:article
)を指定して取得することができます
permited = params.require(:article).permit(:title, :content)
=> #<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: true>
permited.keys
=> ["title", "content"]
複数のキーをrequireする場合には配列として渡してあげる必要があります
permited = params.require(:article, :author)
wrong number of arguments (given 2, expected 1) (ArgumentError)
permited = params.require([:article, :author])
=> [#<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: false>, #<ActionController::Parameters {"name"=>"name"} permitted: false>]
複数のキーでrequireを実行した場合, permitはキーごとに行う必要があります
permited = params.require([:article, :author]).permit(:title, :content, :name)
undefined method `permit` for an instance of Array (NoMethodError)
article_permited, author_permited = params.require([:article, :author])
=> [#<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: false>, #<ActionController::Parameters {"name"=>"name"} permitted: false>]
article_permited.permit(:title, :content)
=> #<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: true>
author_permited.permit(:name)
=> #<ActionController::Parameters {"name"=>"name"} permitted: true>
fetchについて
require
と異なり, 例外ではなくデフォルトを設定することができます
fetch
されたキーがなくなること, permit
の実行が必要なところは一緒になります
例として下記を用います
params = ActionController::Parameters.new({ article: { title: "title", content: "content" }})
=> #<ActionController::Parameters {"article"=>{"title"=>"title", "content"=>"content"}} permitted: false>
require
と一緒でfetch
されたキーがなくなることやpermit
の実行が必要になります
permited = params.fetch(:article).permit(:title, :content)
=> #<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: true>
permited[:article]
=> nil
permited[:title]
=> "title"
permited[:content]
=> "content"
第2引数なしのfetch
はrequire
と同じ挙動になります
つまりfetch
で指定されたキー(:author
)がパラメータにない場合, 例外が発生します
permited = params.fetch(:author).permit(:name)
param is missing or the value is empty or invalid: author (ActionController::ParameterMissing)
第2引数でデフォルトを設定することができます
permited = params.fetch(:author, "author")
=> "author"
第2引数でスカラーを定義するとpermitは出来ません
permited = params.fetch(:author, "author").permit(:name)
undefined method `permit' for an instance of String (NoMethodError)
デフォルトにハッシュを指定することでpermit
できるようになります
permited = params.fetch(:author, {name: "name"}).permit(:name)
=> #<ActionController::Parameters {"name"=>"name"} permitted: true>
permited[:name]
=> "name"
permited = params.fetch(:author, {}).permit(:name)
=> #<ActionController::Parameters {} permitted: true>
permited[:name]
=> nil
require
とは異なり, 複数のキーを指定してfetch
することはできません
params = ActionController::Parameters.new({ article: { title: "title", content: "content" }, author: { name: "name"} })
=> #<ActionController::Parameters {"article"=>{"title"=>"title", "content"=>"content"}, "author"=>{"name"=>"name"}} permitted: false>
# requireみたいに配列で取ってくることはできない
permited = params.fetch([:article, :author])
param is missing or the value is empty: [:article, :author] (ActionController::ParameterMissing)
# 第2引数は特に使われない
permited = params.fetch(:article, :author)
=> #<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: false>
# 第2引数はデフォルトになってしまう
permited = params.fetch(:none, :author)
=> :author
permit, requireとfetchからexpectへ
Strong Parametersでパラメータの許可を行うときはpermit
, パラメータの必須化を行うときはrequire
とfetch
が使われていました
しかしrails8ではパラメータの必須化と許可を同時に行うexpect
が導入されました
これからはexpect
をつかっていくことができます
expectについて
例として下記を用います
params = ActionController::Parameters.new({ article: { title: "title", content: "content" }})
=> #<ActionController::Parameters {"article"=>{"title"=>"title", "content"=>"content"}} permitted: false>
"title"
と"content"
を取得するためには下記のようになります
permitted = params.expect(article: [:title, :content])
=> #<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: true>
:article
は取得できなくなり, :title
と:content
が取得できます
permitted[:article]
=> nil
constraints-project(dev)> permitted[:title]
=> "title"
constraints-project(dev)> permitted[:content]
=> "content"
また, パラメータの許可も行われています
permitted.permitted?
=> true
指定されたキーがない場合にはrequire
と同様に例外が発生します
permitted = params.expect(:author)
param is missing or the value is empty or invalid: author (ActionController::ParameterMissing)
複数のキー(:article, :author
)で構成されているものを考えます
params = ActionController::Parameters.new({ article: { title: "title", content: "content" }, author: { name: "name"} })
=> #<ActionController::Parameters {"article"=>{"title"=>"title", "content"=>"content"}, "author"=>{"name"=>"name"}} permitted: false>
1つのキー(:article
)を指定して取得することができます
permitted = params.expect(article: [:title, :content])
=> #<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: true>
複数キー(:article, :author
)をしてして取得することもできます
permitted = params.expect(article: [:title, :content], author: [:name])
=> [#<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: true>, #<ActionController::Parameters {"name"=>"name"} permitted: true>]
:author
のように1つの要素しかない場合には, 配列で渡さなくても実行できます
permitted = params.expect(article: [:title, :content], author: :name)
=> [#<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: true>, #<ActionController::Parameters {"name"=>"name"} permitted: true
また, 下記のような複雑パラメータでも簡単に必須化と許可を行えます
params = ActionController::Parameters.new({ article: { title: "title", content: "content" }, authors: [{ name: "name1", age: 30}, {name: "name2", age: 35}] })
=> #<ActionController::Parameters {"article"=>{"title"=>"title", "content"=>"content"}, "authors"=>[{"name"=>"name1", "age"=>30}, {"name"=>"name2", "age"=>35}]} permitted: false>
キーの中の配列(:authors
)も簡単に取得することが出来ます
permitted = params.expect(article: [:title, :content], authors: [[:name, :age]])
=>
[#<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: true>,
...
permitted
=>
[#<ActionController::Parameters {"title"=>"title", "content"=>"content"} permitted: true>,
[#<ActionController::Parameters {"name"=>"name1", "age"=>30} permitted: true>, #<ActionController::Parameters {"name"=>"name2", "age"=>35} permitted: true>]]
rails8では二重配列構文[[:属性名]]という新しい配列マッチング構文が導入されています
まとめ
rails8でexpect
が導入されたので簡単にまとめてみました
expect
は必須化と許可を簡単に行うことが出来, 複雑なパラメータも簡単に扱えるようになりました
expcet
はあらゆるパラメータフィルタに対応するわけではありませんが, 簡単に導入できるので試してみる価値がありそうです
ちなみに
今回の変更を取り扱っているプルリクエストは https://github.com/rails/rails/pull/51674 になります
rails8より前はパラメータの処理として
user_params = params.require(:user).permit(:name, :age)
のようにrequire
の後にpermit
を用いることが推奨されていました
しかしときには
user_params = params.permit(user: [:name, :age]).require(:user)
のように先にpermit
する必要もありました
この問題などを解決するために今回の修正が入ったみたいです
参考
railsガイド: https://railsguides.jp/action_controller_overview.html#strong-parameters
ActionController::Parameters: https://api.rubyonrails.org/classes/ActionController/Parameters.html