やること
あるユーザが画像群の中から複数枚を選んでプロフィール画像として設定できるような、次のテーブルがあるとします。特徴としては、結合モデル profile_images
に外部キー以外の属性 order
があることが挙げられます。
これをActive Recordで書くと次のようになります。
class User < ApplicationRecord
has_many :profile_images
has_many :images, through: :profile_images
end
class ProfileImage < ApplicationRecord
belongs_to :users
belongs_to :images
end
# Image は省略
このとき、結合モデル ProfileImage
の属性 order
を取得する場合と、関連先のモデル Image
の属性 src
を取得する場合とで、次のように書き分けなければなりません。
user.profile_images.first.order
user.images.first.src
記述を統一させるために、User
のレコードから関連先 Image
をたどる場合は、次のように ProfileImage
の属性も Image
の属性も統一した形で取得できるようにします。
user.ordered_images.first.order # User からたどると Image から ProfileImage#order を参照できる
user.ordered_images.first.src
やりかた
has_many
の第2引数 (scope
) に次のようなラムダ式を渡します。この引数は関連先を取得するときに使うクエリををカスタマイズするために使います。
class User < ApplicationController
has_many :ordered_images,
->{ select("#{Image.table_name}.*", "#{ProfileImage.table_name}.order AS order") },
through: :profile_images,
class_name: 'Image'
end
※注意: ここでは普通の関連と区別するために ordered_images
という名前にしています。また、この方法を使うと、user.ordered_images.count
のような集約操作はクエリをうまく生成できずエラーになります。
ここでは、テーブル users
, profile_images
, images
をJOINしたテーブルをもとに関連先 Image
のオブジェクト群を取得するときに、ラムダ式内の select
で Image
の属性に加えて ProfileImage.order
を合わせてSELECTするようにしています。
これで user.ordered_images.first.order
のように、User
レコードから関連をたどったときは関連先モデルが結合モデルの属性を持つかのように取り扱うことができます。
参考資料
-
ActiveRecord::Associations::ClassMethods
-
has_many
の説明の "Scopes" を参照のこと
-