Rails: フォームデータのparseとParameterクラスへの変換に関して

前置き

html form の フォームコントロールにおけるname属性に関して、 複数のチェックボックスラジオボタンに同じname属性を指定した場合、 それらは同一のグループに属するものとみなされる。 (HTML解体新書 CHAPTER 3-10 フォームコントロールの共通属性 name属性 より)

例えばこんな感じのcheckbox

またhttpのbody上では、まとめられることなく個別に送信される。

この値をRailsにおいてController等でActionController::Parametersのインスタンスとして受け取った際には、配列の形に変換されている。

ただしこれは input の name属性の最後に [ ] をつけた場合であり、 [ ] がない場合には1つの属性として捉えられ上書きされてしまうようだ。

どうもRailsは最後に[]がついた属性のみ配列としてparseしているらしい。

これがRailsのコード上のどこでこれが行われているのか調べてみた。

どこで行われているか

とりあえずAction Pack...?

とりあえず...Action Packかな? https://github.com/rails/rails/tree/main/actionpack

Action Pack is a framework for handling and responding to web requests. It provides mechanisms for routing (mapping request URLs to actions), defining controllers that implement actions, and generating responses. In short, Action Pack provides the controller layer in the MVC paradigm.

action_dispatch/http あたりにリクエストを処理していそうなやつがいる。 https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/http/request.rb とか。

controllerから

controllerで実行できるrequest.paramsから必要なパラメーターが取得できる。 request.class を実行すると、これがActionDispatch::Requestクラスのものだと分かる。 前項の内容とも一致する。

ActionDispatch::Requestクラスを生成している箇所を見れば、 「Railsは最後に[]がついた属性を配列としてparseしている」 がどこで行われているか判断できそうだ。

Rack

いうてそれがどこか分からない... とりあえずバックトレースを辿ってみることにする。

controllerからcallerを実行すると以下のようなバックトレースが得られた

["lib/ruby/gems/xxx/gems/byebug-xxx/lib/byebug/helpers/eval.rb:61:in `eval'",
 ...
 "lib/ruby/gems/xxx/gems/rack-proxy-xxx/lib/rack/proxy.rb:78:in `call'",
 "lib/ruby/gems/xxx/gems/railties-xxx/lib/rails/engine.rb:539:in `call'",
 "lib/ruby/gems/xxx/gems/puma-xxx/lib/puma/configuration.rb:225:in `call'",
 "lib/ruby/gems/xxx/gems/puma-xxx/lib/puma/server.rb:658:in `handle_request'",
 "lib/ruby/gems/xxx/gems/puma-xxx/lib/puma/server.rb:472:in `process_client'",
 "lib/ruby/gems/xxx/gems/puma-xxx/lib/puma/server.rb:332:in `block in run'",
 "lib/ruby/gems/xxx/gems/puma-xxx/lib/puma/thread_pool.rb:133:in `block in spawn_thread'"]

下の方から見ていくと、

"lib/ruby/gems/2.7.0/gems/railties-6.1.6/lib/rails/engine.rb:539:in `call'",

というものを発見。 pumaから直接呼び出されているrails周りのコードはここ...? 見てみると、

見事にActionDispatch::Requestを作ってる。これかな...?

req.paramsで既にActionController::Parametersが見れたので、 ActionDispatch::Request.new env の処理で作られているんだろう、多分。

POSTメソッドとRack

ActionDispatch::Request の初期化処理周りを見るがよくわからず。 ただ調べているうちにPOST というメソッドがどうもhttpのリクエストのパラメータをparseしているっぽい雰囲気。

parse_formatted_parameters あたりの処理を追っていくと、 最終的にはブロック内のsuperを呼び出しているらしい。 コメントを見る限りRackのPOSTをoverrideしているとのことなので、 Rackに同様のメソッドがあるのか...?

あった。 set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&') のform_varsがURLエンコーディングされたhttpリクエストを保持しており、 parse_query(form_vars, '&')で配列に変わっている

parse_queryを追うと に辿り着く。 細かくは見ていないが[]をチェックしてparamsにhashの形で入れ込んでいる様子。 ここでparamsが形作られているのだろう。

結論

httpリクエストパラメータのparseとActionController::Parameters(or 普通のhash?)への変換は、 どうやらRackがやっているらしい。多分...

所感

まともにRailsのコード追ったのそういえば初めて