2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ギフティAdvent Calendar 2024

Day 10

RailsのStrong Parametersのexpectについて

Last updated at Posted at 2024-12-05

概要

railsではstrong parametersを用いて, 許可されていないパラメータによってモデルの重要な属性が誤って更新されてしまうことを防止することができます
パラメータの許可にはpermitを用います

また必須パラメータが不足している場合, requirefetchを使うことで例外発生やデフォルトの値を設定することができます

さらにrails8からはパラメータの必須化と許可を同時に行うexpectが導入されました

この記事ではpermit, requirefetchついて確認し, rails8で導入されたexpectについて見ていきます

permitについて

permitを用いることで許可するパラメータを決めることができます
スカラー, 配列, ハッシュなどで許可することができます

例として下記のようなparamsを用います
ActionController::Parameters.newで初期化したオブジェクトのpermitedfalseとなっています

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を実行しないと, permittedfalseのままになります
つまり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だけではpermittedfalseのままなので, パラメータを許可するためには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引数なしのfetchrequireと同じ挙動になります
つまり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, パラメータの必須化を行うときはrequirefetchが使われていました
しかし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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?