Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eager load polymorphic

People also ask

What is polymorphic association in Rails?

In Ruby on Rails, a polymorphic association is an Active Record association that can connect a model to multiple other models. For example, we can use a single association to connect the Review model with the Event and Restaurant models, allowing us to connect a review with either an event or a restaurant.

How does eager loading work in Rails?

Eager loading lets you preload the associated data (authors) for all the posts from the database, improves the overall performance by reducing the number of queries, and provides you with the data that you want to display in your views, but the only catch here is which one to use.


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.

  1. ActiveRecord is unable to build the join without additional information.
  2. 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'