Active Record でのポリモーフィック結合


多形関連



Active Record では、polymorphic associations を使用して、モデルが 1 つの関連付けで複数のモデルに属することを許可できます.
bookingaccommodation または office に属する例を次に示します.

class Booking < ApplicationRecord
  belongs_to :bookable, polymorphic: true
end



class Accommodation < ApplicationRecord
  has_many :bookings, as: :bookable
end



class Office < ApplicationRecord
  has_many :bookings, as: :bookable
end


この bookable 関連付けが機能するためには、bookings テーブルが bookable_id 列と bookable_type 列を保持する必要があることに注意してください. Rails の公式ドキュメントには、ポリモーフィック アソシエーションの実装方法が説明されています ( here ).

ポリモーフィック アソシエーションへの参加に関する問題



ポリモーフィック アソシエーションで bookable を直接結合しようとすると、エラーが発生します.

Booking.joins(:bookable)

ActiveRecord::EagerLoadPolymorphicError (Cannot eagerly load the polymorphic association :bookable)


これは、一般的な bookable 関連付けの下に多くのテーブルが存在する可能性があるため、Active Record が結合するテーブルを認識していないためです.

この問題を解決する方法の 1 つは、外部キーと型列を使用して、結合する予約可能なテーブルを明示的に示す SQL 文字列を渡すことです.

Bookings.joins("INNER JOIN accommodations ON accommodations.id = bookings.bookable_id AND bookings.bookable_type = 'Accommodation'")


ただし、クエリから offices テーブルを除外していることに注意してください.これも結合したい場合は、同様の結合ステートメントを追加する必要があります.

Bookings
  .joins("INNER JOIN accommodations ON accommodations.id = bookings.bookable_id AND bookings.bookable_type = 'Accommodation'")
  .joins("INNER JOIN offices ON offices.id = offices.bookable_id AND bookings.bookable_type = 'Office'")

bookable アソシエーションをさらに追加し、その上で追加のクエリを実行する必要がある場合、混乱が生じる可能性があることは容易に想像できます.

これらの冗長な SQL 文字列を渡す代わりに、次のようなことができれば役に立ちます.

Bookings.joins(:accommodation, :office)


すべての予約可能なものに参加したい場合でも、bookable 個の関連付けをすべて渡す必要がありますが、代わりに関連付けをシンボルとして渡すことで実現できます.

ここで実行しようとすると、まだエラーが発生します.

ActiveRecord::ConfigurationError: Can't join 'Booking' to association named 'accommodation'; perhaps you misspelled it?


予約モデルは bookable エンティティのみを認識しているため、Active Record は宿泊施設とオフィスを関連付けとして個別に認識しません.

これらの関連付けを Booking モデルに個別に追加できたらどうでしょうか?

範囲指定された関連付けの使用


bookable_type および foreign_key で範囲を指定することにより、特定の関連付けを定義できます.

class Booking < ApplicationRecord
  belongs_to :bookable, polymorphic: true
  belongs_to :accommodation, -> { where(bookings: { bookable_type: 'Accommodation' }) }, foreign_key: 'accommodation_id'
  belongs_to :office, -> { where(bookings: { bookable_type: 'Office' }) }, foreign_key: 'office_id'
end


ここで、前のクエリを再度実行すると、タイプが「宿泊施設」と「オフィス」のすべての予約が取得されます.内部の SQL は、カスタム結合で以前に記述したものとまったく同じです.クエリで .to_sql メソッドを呼び出すことで確認できます.

Bookings.joins(:accommodation, :office).to_sql

=> "SELECT \"bookings\".* FROM \"bookings\" 
INNER JOIN \"accommodations\" ON \"accomodations\".\"id\" = \"bookings\".\"bookable_id\" AND \"bookings\".\"bookable_type\" = 'Accommodation' 
INNER JOIN \"offices\" ON \"offices\".\"id\" = \"bookings\".\"bookable_id\" AND \"bookings\".\"bookable_type\" = 'Office'


ハッピークエリ!

Active Record への参加の詳細:
  • Understanding Active Record inner joins
  • Understanding Active Record left outer joins
  • Understanding multiple joins in Active Record
  • How to access joined data with Active Record