Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: includes with polymorphic association

I read this interesting article about Using Polymorphism to Make a Better Activity Feed in Rails.

We end up with something like

class Activity < ActiveRecord::Base   belongs_to :subject, polymorphic: true end 

Now, if two of those subjects are for example:

class Event < ActiveRecord::Base   has_many :guests   after_create :create_activities   has_one :activity, as: :subject, dependent: :destroy  end  class Image < ActiveRecord::Base   has_many :tags   after_create :create_activities   has_one :activity, as: :subject, dependent: :destroy  end 

With create_activities defined as

def create_activities   Activity.create(subject: self) end 

And with guests and tags defined as:

class Guest < ActiveRecord::Base   belongs_to :event end  class Tag < ActiveRecord::Base   belongs_to :image end 

If we query the last 20 activities logged, we can do:

Activity.order(created_at: :desc).limit(20) 

We have a first N+1 query issue that we can solve with:

Activity.includes(:subject).order(created_at: :desc).limit(20) 

But then, when we call guests or tags, we have another N+1 query problem.

What's the proper way to solve that in order to be able to use pagination ?

like image 853
Arnaud Avatar asked Feb 25 '14 11:02

Arnaud


People also ask

What are polymorphic associations 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.

What are associations in Rails?

Association in Rails defines the relationship between models. It is also the connection between two Active Record models. To figure out the relationship between models, we have to determine the types of relationship. Whether it; belongs_to, has_many, has_one, has_one:through, has_and_belongs_to_many.


Video Answer


2 Answers

Edit 2: I'm now using rails 4.2 and eager loading polymorphism is now a feature :)

Edit: This seemed to work in the console, but for some reason, my suggestion of use with the partials below still generates N+1 Query Stack warnings with the bullet gem. I need to investigate...

Ok, I found the solution ([edit] or did I ?), but it assumes that you know all subjects types.

class Activity < ActiveRecord::Base   belongs_to :subject, polymorphic: true    belongs_to :event, -> { includes(:activities).where(activities: { subject_type: 'Event' }) }, foreign_key: :subject_id   belongs_to :image, -> { includes(:activities).where(activities: { subject_type: 'Image' }) }, foreign_key: :subject_id end 

And now you can do

Activity.includes(:part, event: :guests, image: :tags).order(created_at: :desc).limit(10) 

But for eager loading to work, you must use for example

activity.event.guests.first 

and not

activity.part.guests.first 

So you can probably define a method to use instead of subject

def eager_loaded_subject   public_send(subject.class.to_s.underscore) end 

So now you can have a view with

render partial: :subject, collection: activity 

A partial with

# _activity.html.erb render :partial => 'activities/' + activity.subject_type.underscore, object: activity.eager_loaded_subject 

And two (dummy) partials

# _event.html.erb <p><%= event.guests.map(&:name).join(', ') %></p>  # _image.html.erb <p><%= image.tags.first.map(&:name).join(', ') %></p> 
like image 188
Arnaud Avatar answered Oct 06 '22 06:10

Arnaud


This will hopefully be fixed in rails 5.0. There is already an issue and a pull request for it.

https://github.com/rails/rails/pull/17479

https://github.com/rails/rails/issues/8005

I have forked rails and applied the patch to 4.2-stable and it works for me. Feel free to use my fork, even though I cannot guarantee to sync with upstream on a regular basis.

https://github.com/ttosch/rails/tree/4-2-stable

like image 43
tosch Avatar answered Oct 06 '22 06:10

tosch