LoginSignup
9
3

More than 3 years have passed since last update.

ActiveRecord includesとselectの別名カラム

Last updated at Posted at 2021-03-01

概要

ActiveRecord includesを使う際、selectに別名カラムの記述があると
その別名カラムの参照ができない事象があったので、記事にしました。

開発環境

  • ruby 2.6.5
  • Rails 5.2.4.4

ActiveRecordクラス 定義

class User < ApplicationRecord
  has_one    :house
end

class House < ApplicationRecord
  belongs_to    :user
  belongs_to    :house_info
end

class HouseInfo < ApplicationRecord
  has_one    :house
end

問題ない場合のincludes構文

  • 問題なくuserインスタンスが取得できた (preloadの挙動になる)
User.includes(house: :house_info).select('users.id, users.created_at as user_registed_at')

SELECT  users.id, users.created_at as user_registed_at FROM "users"
SELECT "houses".* FROM "houses" WHERE "houses"."user_id" = $1

 [#<User:0x000055a87e374e58 id: 1>,
 #<User:0x000055a87e3742c8 id: 2>,
 #<User:0x000055a87e37f858 id: 3>,
 #<User:0x000055a87e37ecc8 id: 4>,
 #<User:0x000055a87e37e1b0 id: 5>]
  • selectで指定した別名カラムの値も取得できる
User.includes(house: :house_info).select('users.id, users.created_at as user_registed_at').first.user_registed_at

SELECT  users.id, users.created_at as user_registed_at FROM "users"
SELECT "houses".* FROM "houses" WHERE "houses"."user_id" = $1
=> "2021-02-05T13:02:17.754+09:00"

問題ありのincludes構文

includesしたテーブルに対し、検索条件(where)を追記してみた

  • house_infoのownerカラムをwhere条件にいれると left outter joinとなり、テーブルそれぞれのカラムが別名のsqlが発行される(eager_loadの挙動になる)
User.includes(house: :house_info).where(house_infos: { owner: '太郎' }).select('users.id, users.created_at as user_registed_at')

SELECT  users.id, users.created_at as user_registed_at, "users"."id" AS t0_r0, "users"."name" AS t0_r1, "interviews"."creator_id" AS t0_r2, "houses"."user_id" AS t0_r3, "houses"."house_info_id" AS t0_r4, "house_infos"."address" AS t0_r5, "house_infos"."owner" AS t0_r6
FROM "users" LEFT OUTER JOIN "houses" ON "houses"."interview_id" = "users"."id" LEFT OUTER JOIN "house_infos" ON "house_infos"."id" = "houses"."house_info_id" WHERE "house_infos"."owner" = '太郎'

[#<User:0x000055a879e296c8
  id: 1,
  created_at: 2020/12/12>
]
  • select で別名指定した user_registed_atはsql上、存在するが、モデルから参照できない。
User.includes(house: :house_info).where(house_infos: { owner: '太郎' }).select('users.id, users.created_at as user_registed_at').first.user_registed_at

NoMethodError: undefined method `user_registed_at' for #<User:0x000055a87c1a93c8>
from /bundle/gems/activemodel-5.2.4.4/lib/active_model/attribute_methods.rb:430:in `method_missing'

includesをやめて解決

  • househouse_infoを joinし、preloadでキャッシュするようにした
User.joins(house: :house_info).preload(house: :house_info).where(house_infos: { owner: '太郎' }).select('users.id, users.created_at as user_registed_at')

SELECT users.id, users.created_at as user_registed_at FROM "users" INNER JOIN "houses" ON "houses"."user_id" = "users"."id" INNER JOIN "house_infos" ON "house_infos"."id" = "houses"."house_info_id" WHERE "house_infos"."owner" = '太郎'

SELECT "houses".* FROM "houses" WHERE "houses"."user_id" IN ($1, $2, $3, $4, $5)

SELECT "house_infos".* FROM "house_infos" WHERE "house_infos"."id" IN ($1, $2, $3, $4, $5)

[#<User:0x000055a87d6fb410 id: 1>,
 #<User:0x000055a87d6fa8f8 id: 2>,
 #<User:0x000055a87d6f9de0 id: 3>,
  .
  .
  • 各モデルから user_registed_atが参照できるようになった。
User.joins(house: :house_info).preload(house: :house_info).where(house_infos: { owner: '太郎' }).select('users.id, users.created_at as user_registed_at').first.user_registed_at

=> "2021-02-05T13:05:47.225+09:00"

includesの挙動について調べてみた

  • includesはjoinsがなければpreloadと同じ挙動となる
  • 明示的にeager_loadと同じ挙動にするためにはreferencesというメソッドを併用する必要

User.includes(house: :house_info).references(house: :house_info).select('users.id, users.created_at as user_registed_at')

SELECT  users.id, users.created_at as user_registed_at, "users"."id" AS t0_r0, "users"."name" AS t0_r1, "interviews"."creator_id" AS t0_r2, "houses"."user_id" AS t0_r3, "houses"."house_info_id" AS t0_r4, "house_infos"."address" AS t0_r5, "house_infos"."owner" AS t0_r6
FROM "users" LEFT OUTER JOIN "houses" ON "houses"."interview_id" = "users"."id" LEFT OUTER JOIN "house_infos" ON "house_infos"."id" = "houses"."house_info_id"

なぜ?別名カラムをモデルから参照できなかったのか?

https://github.com/rails/rails/issues/34889
バグだったらしく、Rails6.0.3で治っているよう。

最後に

includesはケースによって
preloadまたはeager_loadの挙動をするのは知っていたが、
selectで指定した別名カラムの参照問題は知らず、、1日ハマっていた。
やっぱり、ActiveRecordはハマりポイントが多そう。

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3