はじめに
この記事は株式会社ラグザイア Advend Calendar 2023 の記事です
Ruby on Railsを用いて実装を行っているとき、controller内でよくparamsメソッドを用いて、リクエストで送られてきたデータを受け取るということをやると思います
ふと、paramsは何者で、なぜリクエストで送られてきたデータを取り出すことができるのだろう? と興味が湧いたので調べてみました
調査
https://api.rubyonrails.org/classes/ActionController/Metal.html#method-i-params-3D によると、paramsはActionController::Metal※1 のインスタンスメソッドで、以下のように定義されていました
def params
@_params ||= request.parameters
end
@_paramsがnilでなければそのまま返し、nilの場合はrequest.parameters※2を代入して返すというものです
続いて、@_paramsのソースコードを調べてみると、ActionController::Metalのinitializeに定義されていました(https://github.com/rails/rails/blob/c80fa6a0697656a522b7688d116a9a6fca1fc0fd/actionpack/lib/action_controller/metal.rb#L185)
def initialize
@_request = nil
@_response = nil
@_response_body = nil
@_routes = nil
@_params = nil
super
end
ここまでで、
ということがわかりました。
次に、実際に手元で、リクエスト時に@_paramsに何が入るのかをみてみます
準備
rails newで検証のためのrailsアプリを作成する。アプリ名は「params_inspection」とする
params_inspects_controller.rbとparams_inspectアクションを作成し、中にdebuggerを仕込む
def params_inspect
debugger
end
routes.rbにルート追加
get 'params_inspects/params_inspect', to: 'params_inspects#params_inspect'
これで準備ok
また、使用した環境は以下です
mac OS Ventura 13.6.1
Ruby 3.1.4
Ruby on Rails 7.1.2
実践
rails sでサーバーを立ち上げた後、postmanでリクエストを飛ばし、railsのログ内でデバッガを使ってインスタンス変数(@_params)の中身を見てみます
Started GET "/params_inspects/params_inspect?hoge=foo_1" for ::1 at 2023-12-09 14:48:57 +0900
Processing by ParamsInspectsController#params_inspect as */*
Parameters: {"hoge"=>"foo_1"}
[1, 6] in ~/params_inspection/app/controllers/params_inspects_controller.rb
1| class ParamsInspectsController < ApplicationController
2| def params_inspect
=> 3| debugger
4| Rails.logger.debug "Received params: #{params.inspect}"
5| end
6| end
=>#0 ParamsInspectsController#params_inspect at ~/params_inspection/app/controllers/params_inspects_controller.rb:3
#1 ActionController::BasicImplicitRender#send_action(method="params_inspect", args=[]) at ~/.rbenv/versions/3.1.4/lib/ruby/gems/3.1.0/gems/actionpack-7.1.2/lib/action_controller/metal/basic_implicit_render.rb:6
# and 79 frames (use `bt' command for all frames)
(rdbg) p params # command
=> #<ActionController::Parameters {"hoge"=>"foo_1", "controller"=>"params_inspects", "action"=>"params_inspect"} permitted: false>
(rdbg) p @_request # command
=> #<ActionDispatch::Request GET "http://localhost:3000/params_inspects/params_inspect?hoge=foo_1" for ::1>
(rdbg) p instance_variables # command
=> [:@_action_has_layout, :@rendered_format, :@_routes, :@_request, :@_response, :@_response_body, :@_params, :@_url_options, :@marked_for_same_origin_verification, :@_view_runtime, :@_db_runtime, :@_lookup_context, :@_action_name, :@_config]
(rdbg) p instance_variable_get('@_params') # command
=> #<ActionController::Parameters {"hoge"=>"foo_1", "controller"=>"params_inspects", "action"=>"params_inspect"} permitted: false>
@_paramsインスタンス変数に、リクエストのパラメータが格納されたActionController::Parametersインスタンスが入っていることがわかりました
まとめ
今回のまとめです
- paramsメソッドは、@_paramsを参照するメソッド
- @_paramsはActionControllerインスタンスが作成される時に定義されるインスタンス変数
- リクエスト後、@_paramsには、リクエストのパラメータが格納されたActionController::Parametersインスタンスが入っている
今度は、httpプロトコルで送られたリクエストがどのように解析されて、最終的にパラメーターがActionController::Parametersインスタンスに格納されるのか?を調べてみたいと思います
ここまで読んでいただき、ありがとうございました!
注釈
※1 ActionController::BaseはActionController::Metalを継承している
※2 request.parametersは ActionDispatch::Request
オブジェクトのメソッドで、クエリパラメータ、リクエストボディ(POSTデータ)、ルートパラメータなど、リクエストに含まれるすべてのパラメータを結合して返す(https://github.com/rails/rails/blob/c80fa6a0697656a522b7688d116a9a6fca1fc0fd/actionpack/lib/action_dispatch/http/parameters.rb#L50)