Eager load polymorphic
My guess is that your models look like this:
class User < ActiveRecord::Base
has_many :reviews
end
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :reviewable, polymorphic: true
end
class Shop < ActiveRecord::Base
has_many :reviews, as: :reviewable
end
You are unable to do that query for several reasons.
- ActiveRecord is unable to build the join without additional information.
- There is no table called reviewable
To solve this issue, you need to explicitly define the relationship between Review
and Shop
.
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :reviewable, polymorphic: true
# For Rails < 4
belongs_to :shop, foreign_key: 'reviewable_id', conditions: "reviews.reviewable_type = 'Shop'"
# For Rails >= 4
belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
# Ensure review.shop returns nil unless review.reviewable_type == "Shop"
def shop
return unless reviewable_type == "Shop"
super
end
end
Then you can query like this:
Review.includes(:shop).where(shops: {shop_type: 'cafe'})
Notice that the table name is shops
and not reviewable
. There should not be a table called reviewable in the database.
I believe this to be easier and more flexible than explicitly defining the join
between Review
and Shop
since it allows you to eager load in addition to querying by related fields.
The reason that this is necessary is that ActiveRecord cannot build a join based on reviewable alone, since multiple tables represent the other end of the join, and SQL, as far as I know, does not allow you join a table named by the value stored in a column. By defining the extra relationship belongs_to :shop
, you are giving ActiveRecord the information it needs to complete the join.
If you get an ActiveRecord::EagerLoadPolymorphicError, it's because includes
decided to call eager_load
when polymorphic associations are only supported by preload
. It's in the documentation here: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html
So always use preload
for polymorphic associations. There is one caveat for this: you cannot query the polymorphic assocition in where clauses (which makes sense, since the polymorphic association represents multiple tables.)
This did the work for me
belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
Not enough reputation to comment to extend the response from Moses Lucas above, I had to make a small tweak to get it to work in Rails 7 as I was receiving the following error:
ArgumentError: Unknown key: :foreign_type. Valid keys are: :class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading, :autosave, :required, :touch, :polymorphic, :counter_cache, :optional, :default
Instead of belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
I went with belongs_to :shop, class_name: 'Shop', foreign_key: 'reviewable_id'
The only difference here is changing foreign_type:
to class_name:
!