curl -X POST 'http://localhost:3000/wrap_parameter/outputs' -H "Content-Type: application/json" -d '{"name":"foo", "age": 20}'
なリクエストを投げたところ、ログに
Started POST "/wrap_parameter/outputs" for 127.0.0.1 at 2022-05-23 22:26:50 +0900 Processing by WrapParameter::OutputsController#create as */* Parameters: {"name"=>"foo", "age"=>20, "output"=>{"name"=>"foo", "age"=>20}}
と出力されていました。
Parameters
の構造をよく見ると
{ "name" => "foo", "age" => 20, # 追加されている "output" => { "name" => "foo", "age" =>20 } }
と、リクエストしたデータ '{"name":"foo", "age": 20}'
には無いキー output
が自動で追加されていました。
なぜこれが追加されているのか気になったので、調べたときのメモを残します。
目次
環境
- Rails 7.0.3
wrap_parameterによるキーの自動追加について
自動で追加されるキーについてRailsガイドを探してみたところ、以下の記載がありました。
4.2 JSONパラメータ | Action Controller の概要 - Railsガイド
データの送信先がCompaniesControllerであれば、以下のように:companyというキーでラップされます。
{ name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } }
そのため、自動的にキーが追加されるのは wrap_parameters
機能によるものだったようです。
wrap_parametersの詳細は、APIドキュメントにも記載されています。
ActionController::ParamsWrapper
改めて、今回 output
というキーが自動追加されたコントローラを見ると
class WrapParameter::OutputsController < ApplicationController def create # 略 head :created end end
となっていました。
コントローラ名が OutputsController
なため、 output
というキーが追加されたと分かりました。
wrap_parametersとStrong Parametersについて
ここまでで、「自動でキーを追加したのは wrap_parameters
機能」と分かりました。
次は wrap_parameters で追加されたキーに対して、Strong Parametersの require
や permit
を使って値を取り出してみます。
準備
今回は前述のコントローラ OutputsController
を使って動作確認することにします。
そこで、動作確認しやすくするよう
- CSRF対策を除外
- 取り出した値を出力するメソッド
write_log
を用意
をコントローラに追加します。
class WrapParameter::OutputsController < ApplicationController # POSTする時にCSRFで引っかかってしまうので、今回は外しておく protect_from_forgery except: :create def create output_params head :created end private def output_params # ここに確認用の処理を書く end private def write_log(values) logger.info('============>') logger.info(values) logger.info('<============') end end
これで準備ができました。
requireやpermitを使わずに、paramsから値を取り出す
準備ができたので、リクエストパラメータが含まれる params
より値を取り出します。
4 パラメータ | Action Controller の概要 - Railsガイド
まずは require
や permit
を使わずに、paramsから値を取り出してみます。
今回は、curlで
curl -X POST 'http://localhost:3000/wrap_parameter/outputs' -H "Content-Type: application/json" -d '{"name":"foo", "age": 20}'
と OutputsController にHTTPリクエストし、その結果を取り出してみます。
コントローラでparamsを確認できるような実装をした上で curl を実行したところ、
# すべて write_log(params) # => {"name"=>"foo", "age"=>20, "controller"=>"wrap_parameter/outputs", "action"=>"create", "output"=>{"name"=>"foo", "age"=>20}} # キーを指定 write_log(params[:name]) # => foo # wrap parametersのキーを指定 write_log(params[:output]) # => {"name"=>"foo", "age"=>20}
という結果が出力されました。
これにより、wrap_parametersで追加されたキーであっても、普通のキー同様値を取り出せることが分かりました。
Strong Parametersで取り出す
続いて、Strong Parametersの機能を使って取り出してみます。
requireやpermitで複雑な形のデータを取り出せるか確認するため、curlでネストしているデータをPOSTしてみます。
curl -X POST 'http://localhost:3000/wrap_parameter/outputs' -H "Content-Type: application/json" -d '{"name":"foo", "age": 20, "pages": [1,2,3], "article": {"title": "hello", "authors": [{"name": "bob", "age": 25, "pages": [1,3]}, {"name": "alice", "age": 30, "pages": [2,4]}]}}'
curlのワンライナーだとどんなデータか分かりづらいため、データ部分のみ整形したのが以下です。
{ "name":"foo", "age":20, "pages":[ 1, 2, 3 ], "article":{ "title":"hello", "authors":[ { "name":"bob", "age":25, "pages":[ 1, 3 ] }, { "name":"alice", "age":30, "pages":[ 2, 4 ] } ] } }
これを使って確認します。
permitだけ使う
まずは permit
だけを指定して、値を取り出してみます。
write_log(params.permit(:name, :age)) # => {"name"=>"foo", "age"=>20}
取り出せました。
なお、permitしてないパラメータについては、ログに出力されていました。
Unpermitted parameters: :name, :age, :article, :output. Context: { controller: WrapParameter::OutputsController, action: create, request: #<ActionDispatch::Request:0x000000010ac06380>, params: {"name"=>"foo", "age"=>20, "pages"=>[1, 2, 3], "article"=>{"title"=>"hello", "authors"=>[{"name"=>"bob", "age"=>25, "pages"=>[1, 3]}, {"name"=>"alice", "age"=>30, "pages"=>[2, 4]}]}, "controller"=>"wrap_parameter/outputs", "action"=>"create", "output"=>{"name"=>"foo", "age"=>20, "pages"=>[1, 2, 3], "article"=>{"title"=>"hello", "authors"=>[{"name"=>"bob", "age"=>25, "pages"=>[1, 3]}, {"name"=>"alice", "age"=>30, "pages"=>[2, 4]}]}}} }
他に
- 配列
- ネストしたオブジェクト
- 複雑なネスト
を試してみます。
## 配列 write_log(params.permit(pages: [])) # => {"pages"=>[1, 2, 3]} ## ネストしたオブジェクト write_log(params.permit(article: [:title, :content])) # => {"article"=>#<ActionController::Parameters {"title"=>"hello"} permitted: true>} ## 複雑なネスト write_log(params.permit(:name, :age, article: [:title, :content, authors: [:name, pages: []]])) # => {"name"=>"foo", # "age"=>20, # "article"=>#<ActionController::Parameters { # "title"=>"hello", # "authors"=>[ # #<ActionController::Parameters {"name"=>"bob", "pages"=>[1, 3]} permitted: true>, # #<ActionController::Parameters {"name"=>"alice", "pages"=>[2, 4]} permitted: true> # ] # } permitted: true> # }
いずれも取得できました。
require + permit を使う
最後に、 wrap_parametersで追加されたキーに対して、 require
+ permit
を使って値を取り出してみます。
同じように値が取り出せました。
## フラット write_log(params.require(:output).permit(:name, :age)) # => {"name"=>"foo", "age"=>20} ## 配列 write_log(params.require(:output).permit(pages: [])) # => {"pages"=>[1, 2, 3]} ## ネストしたオブジェクト write_log(params.require(:output).permit(article: [:title, :content])) # => {"article"=>#<ActionController::Parameters {"title"=>"hello"} permitted: true>} ## もう一回ネスト write_log(params.require(:output).permit(:name, :age, article: [:title, :content, authors: [:name, pages: []]])) # => {"name"=>"foo", # "age"=>20, # "article"=>#<ActionController::Parameters { # "title"=>"hello", # "authors"=>[ # #<ActionController::Parameters {"name"=>"bob", "pages"=>[1, 3]} permitted: true>, # #<ActionController::Parameters {"name"=>"alice", "pages"=>[2, 4]} permitted: true> # ] # } permitted: true> # }
ソースコード
Githubに上げました。
https://github.com/thinkAmi-sandbox/rails_7_0_minimal_app
今回のPRはこちらです。
https://github.com/thinkAmi-sandbox/rails_7_0_minimal_app/pull/1