記事執筆に至る経緯
以下のように複数のテーブルをjoinし、特定のカラムを選択したActiveRecord::Relationオブジェクトの内容を確認しようとppを使ってログ出力してみました。
relation = Staff.joins(:division).merge(Division.where(division_id: 1)).select(:staff_name, :division_name)
pp relation
すると以下のような結果に。
# [#<Staff:0x00003b9efdp771d8 staff_name: 'スタッフ太郎'>]
あれ、staff_nameしか出てきてない。取得できていないのか。。
いやでもデータは取得できてそう。。なんでや??
この状況で先輩に質問した時に教えてもらったのが.as_jsonでした。
pp relation.as_json
このように書くと、以下のようにJSON形式できちんとselectした値が確認できる。
# [{"staff_name"=>"スタッフ太郎","division_name" => "テスト部署"}]
なぜppでは出てこないんだ。。あ、調べて記事にするかーーー。という経緯でこの記事を執筆しています。
調査開始
仮説1: ActiveRecord::Relationオブジェクトの特性かも?
ActiveRecord::Relationオブジェクトから個別のレコード(ActiveRecordモデルのインスタンス)を取得し、このオブジェクトをppでログ出力してみました。しかし、期待した追加のカラム(この例ではdivision_name)が表示されませんでした。
どうやらActiveRecord::Relationに限った話ではなさそうなので、この仮説は間違いです。
pp relation[0]
# 出力結果
#<Staff:0x00003b9efdp771d8 staff_name: 'スタッフ太郎'>
仮説2:そもそもppでレコードは読み込まれていないから出力されないのでは?
relation = Staff.joins(:division).merge(Division.where(division_id: 1)).select(:staff_name, :division_name)
puts relation.loaded? # => false
pp relation
puts relation.loaded? # => true
検証してみた結果、ppでオブジェクトをログに出力するとレコードの読み込みが行われています。以下のサイトにも同様の記述があり、この仮説も正しくないことが判明しました。
調査結果
以上の結果から、
ActiveRecordがどのようにデータをオブジェクトとしてマッピングしているか
が今回の現象に起因しているのではないかと思いました。
調べてみると、ActiveRecordは、デフォルトでクエリの結果をモデルのインスタンスにマッピングする際、そのモデルのテーブルに定義されているカラムに基づいてマッピングを行うようです。
今回の例だと、Staffモデルのインスタンスには、Staffテーブルのカラムが属性として自動的にマッピングされますが、Divisionテーブルのdivision_nameなど、関連する他のテーブルから選択したカラムは自動的にはマッピングされません。
ですので、ppでrelationをログ出力した場合、Staffモデルのインスタンスを出力することになるのでstaffテーブルに定義されているカラムの情報のみが出力されていました。
ではなぜas_jsonメソッドだと全て出力されるのか
as_jsonメソッドを使用すると、オブジェクトが保持するデータをハッシュマップに変換し、関連するテーブルのデータも含めて現在のオブジェクトが持つ全てのデータをロードしようとします。結果として、StaffとDivisionのデータが含まれたJSONが生成され、ログ出力されます。
終わりに
ここまで読んでいただきありがとうございました!
ActiveRecordは何となくで使えてしまうので、その仕組みをしっかり理解した上で使うことが大切だなと改めて痛感しました。
もし間違い等ありましたら、ご指摘くださると幸いです。
参考文献