こんにちは、hachi8833です。Rails 8で追加されたStrong Parametersのexpect
と二重配列構文について自分でもまとめてみました。
🔗 概要
Rails 8では、以下のプルリクでParameters#expect
メソッドが新しく追加されました。
これにより、従来はActionController::Parameters
のrequire
とpermit
の2本立てで記述していたstrong parametersを、expect
でまとめて書けるようになります。
# 従来
params.require(:user).permit(:name, :age)
# Rails 8: 上は以下と同等
params.expect(user: [:name, :age])
expect
メソッドの追加と同時に、独自の[[:属性名]]
という二重配列記法も新たに導入されました。
また、expect
に加えてexpect!
メソッドも追加されています。
expect
で発生するエラーはhandled errorとして扱われるので"400 Bad request"ページが表示されますが、expect!
で発生するエラーは、unhandled errorを発生して"500 Internal Server Error"ページが表示されます。
参考: 500 Internal Server Error - HTTP | MDN
参考: 400 Bad Request - HTTP | MDN
また、この改修に伴って、Railsガイドのstrong parametersに関する記述もRails 8でかなり更新されました↓。
参考: §5 Strong Parameters -- Action Controller の概要 - Railsガイド
🔗 1: expect
を使うとpermit
とrequire
よりもシンプルに書ける
従来のstrong parametersでは、省略不可の必須キーをrequire
で指定し、許可したい属性をpermit
で指定する方法が使われていました。
# 従来のpermit+requireによる書き方
params.require(:person).permit(:name, :age)
Rails 8.0のexpect
を使うと、上と同等のことを以下のように簡潔に書けます。
# expectで書く場合
params.expect(person: [:name, :age])
上の(:name, :age)
や[:name, :age]
は配列を返すという意味ではなく、{ name: "John Doe", age: 42 }
のような、スカラー値を持つハッシュを返すべきであることを意味しています。
なお、従来のpermit
とrequire
によるStrong Parametersでネストするときの書き方については、以下の記事が参考になります。
参考: ネストするStrong Parametersの書きかた #Rails - Qiita
🔗 2: params
の構造も詳細に強制できるようになった
expect
と二重配列記法[[:属性名]]
(後述)が導入されたことで、strong parametersで以下のような複雑にネストした属性でハッシュと配列を区別して指定することも可能になりました。詳しくは後述の二重配列記法を参照してください。
# Railsガイド「Action Controller の概要」より
# 期待されるパラメータの例:
params = ActionController::Parameters.new(
name: "Martin",
emails: ["me@example.com"],
friends: [
{ name: "André", family: { name: "RubyGems" }, hobbies: ["keyboards", "card games"] },
{ name: "Kewe", family: { name: "Baroness" }, hobbies: ["video games"] },
]
)
# パラメータは以下のexpectによって許可済みであることが保証される:
name, emails, friends = params.expect(
:name, # 許可済みのスカラー値
emails: [], # 許可済みのスカラー値の配列
friends: [[ # 許可済みのParameterハッシュの配列
:name, # 許可済みのスカラー値
family: [:name], # family: { name: "許可済みのスカラー値" }
hobbies: [] # 許可済みのスカラー値の配列
]]
)
🔗 3: expect
は500エラーを削減できる
従来のRailsにおけるrequire
とpermit
は、以下のようにrequire
とpermit
の順にチェインすると、たとえばパラメータにハッシュではなくスカラー値が渡されたときにNoMethodError
、つまり"500 Internal Server Error"ページが表示されました。これは嬉しくありませんね。
Parameters.new(user: "hax").require(:user).permit(:name, :email)
#=> undefined method `permit' for an instance of String (NoMethodError)
従来のRailsでも、たとえば以下のようにpermit
、require
の順にチェインすることで、ParameterMissing
にすることは「一応」可能です(デフォルトでは"400 Bad Request"を表示します)。
Parameters.new(user: "hax").permit(:name, :email).require(:user)
#=> param is missing or the value is empty or invalid: user (ActionController::ParameterMissing)
しかし新しいexpect
なら、上のような工夫をしなくても、デフォルトで後者のParameterMissing
になります。"500 Internal Server Error"が必要ならexpect!
を使えば済みます。
Parameters.new(user: "hax").expect(user: [:name, :email])
#=> param is missing or the value is empty or invalid: user (ActionController::ParameterMissing)
詳しくは以下の記事をご覧ください。
🔗 4: 新しい二重配列記法[[:属性名]]
🔗 従来のpermit
の振る舞い
従来のstrong parametersでpermit
->require
の順にチェインする場合は、permit
で指定する属性をpermit(user: [:name])
のように書くことで、user
キーの値をハッシュまたは配列のどちらかに許容する指定が可能でした。
しかし言い換えれば、この方法ではpermit
で「ハッシュのみ」や「配列のみ」は指定できません。これでは、複雑にネストした属性を精密にチェックできません。
# 従来のpermit: [:name]にはハッシュを渡せる
Parameters.new(user: { name: "Martin" }).permit(user: [:name]).require(:user)
# => {"name"=>"Martin"}
# 従来のpermit: [:name]には配列も渡せる
Parameters.new(user: [{ name: "Martin" }]).permit(user: [:name]).require(:user)
# => [{"name"=>"Martin"}]
(本記事では戻り値のpermitted: false
は省略しています)
🔗 8.0のexpect
では[[:属性名]]
で配列を指定できるようになった
新しいexpect
では、新登場の二重配列記法[[:属性名]]
を使うと、属性を(ハッシュではなく)配列1にすることを厳密に要求できるようになります。これにより、パラメータのネスト構造に縛りをかけやすくなっています。
[[:属性名]]
: その属性は配列でなければならない
# expectの場合: [[:name]]は配列でなければならない
Parameters.new(user: [{ name: "Martin" }]).expect(user: [[:name]])
# => {"name"=>"Martin"}
# ハッシュを渡すとエラーになる
Parameters.new(user: { name: "Martin" }).expect(user: [[:name]])
# => param is missing or the value is empty: user (ActionController::ParameterMissing)
さらに、従来の[:属性名]
は、expect
ではその属性をハッシュにすることを厳密に要求するのに使われます。
[:属性名]
: その属性はハッシュでなければならない
# expectの場合: [:name]はハッシュでなければならない
Parameters.new(user: { name: "Martin" }).expect(user: [:name])
# => {"name"=>"Martin"}
# 配列を渡すとエラーになる
Parameters.new(user: [{ name: "Martin" }]).expect(user: [:name])
# => param is missing or the value is empty or invalid: user (ActionController::ParameterMissing
🔗 参考: 8.0のpermit
の場合
Rails 8.0のpermit
で二重配列記法[[:属性名]]
を試してみたところ、厳密に配列を要求するようになっていました↓。
# permitで[[:属性名]]を指定してからハッシュを渡すとエラーになる
Parameters.new(user: { name: "Martin" }).permit(user: [[:name]]).require(:user)
# => param is missing or the value is empty or invalid: user (ActionController::ParameterMissing)
# permitで[[:属性名]]を指定してから配列を渡すのはOK
Parameters.new(user: [{ name: "Martin" }]).permit(user: [[:name]]).require(:user)
# => [{"name"=>"Martin"}]
また、後方互換性のため、permit
に[:属性名]
を指定した場合は、従来通りハッシュと配列のどちらを渡しても許容されます↓。
# 以下はどちらも許容される
# permitで[:属性名]にハッシュを渡した場合
Parameters.new(user: { name: "Martin" }).permit(user: [:name]).require(:user)
# => {"name"=>"Martin"}
# permitで[:属性名]に配列を渡した場合
Parameters.new(user: [{ name: "Martin" }]).permit(user: [:name]).require(:user)
# => [{"name"=>"Martin"}]
参考までに、Rails 7.2以下でpermit
に二重配列記法[[:属性名]]
を書いてみたところ、エラーにならず、通常の[:属性名]
と同じ振る舞いになります。
# Rails 7.2での実行結果:
Parameters.new(user: { name: "Martin" }).permit(user: [:name]).require(:user)
#=> {"name"=>"Martin"}
Parameters.new(user: { name: "Martin" }).permit(user: [[:name]]).require(:user)
#=> {"name"=>"Martin"}
Parameters.new(user: [{ name: "Martin" }]).permit(user: [:name]).require(:user)
#=> [{"name"=>"Martin"}]
Parameters.new(user: [{ name: "Martin" }]).permit(user: [[:name]]).require(:user)
#=> [{"name"=>"Martin"}]
🔗 今後はどちらを使えばよいか?
上述したように、Rails 8のガイドではサンプルコードのrequire
やpermit
がexpect
に変更されています。
expect
のAPIドキュメント↓にも、従来のrequire
やpermit
による方式よりも好ましいと書かれています。
一方、従来のrequire
やpermit
を廃止するような動きは今のところ特になさそうです。たぶん今後もしないのではと予想しています。
現在動いているrequire
やpermit
の置き換えをそれほど急ぐ必要はないかもしれませんが、この機能を実装したMartin Emdeさんの記事では「オプショナルなパラメータでない限り、基本的にexpect
に置き換える方がよい」とのことです↓。置き換えることで、不要な500エラーが減ることも期待できます。
また、以下の動きを見ても、今後はexpect
を推す方向に向かっているようです。
- Rails 8のscaffoldで生成されるコントローラでも、
require
やpermit
の代わりにデフォルトでexpect
が使われるようになりました。 -
Rails 8のガイドのstrong parametersでは、従来の
require
やfetch
を使う書き方が削除されています。また、permit
は、スカラー値の指定方法の説明部分以外では削除されています。
参考: 4.6 Strong Parameters -- Action Controller の概要 - Railsガイド
関連記事
-
厳密には
[{ name: "Martin" }]
のような「ハッシュの配列」ですが、本記事では煩雑さを避けるため「配列」と略記します。 ↩
なお、パラメータが空の場合のみ、scaffoldで生成されるコントローラで
expect
の代わりにfetch
を使うよう微修正が入りました↓参考: Revert params.fetch to params.expect conversions in scaffold by martinemde · Pull Request #52932 · rails/rails