How do I optimize a polymorphic news feed in rails? - ruby-on-rails

Here is my model:
class User < ActiveRecord::Base
has_many :activities
has_many :requests
class Activity < ActiveRecord::Base
belongs_to :user
belongs_to :object, :polymorphic => true
I want to get all the users activities and display them
Activity.where(:user_id => current_user.id).include(:object)
the problem is that I can't eager load the object model because it's polymorphic
How do I overcome this problem?

Eager loading is supported with polymorphic associations. You will need to do something along the following lines:
Activity.find(:all, :include => :objectable, :conditions => {:user_id => current_user.id})
Although you need to make sure that you have defined the polymorphic relationship correctly on the associated models.
For further help refer to:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Eager+loading+of+associations
The polymorphic part is at the end of "Eager loading of Associations" section.

As #Wahaj says, eager loading only works with :includes and not :join.
Here's the explanation from the docs:
Address.find(:all, :include => :addressable)
This will execute one query to load the addresses and load the addressables with one query per addressable type. For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent model’s type is a column value so its corresponding table name cannot be put in the FROM/JOIN clauses of that query.
I think this is what you're after:
current_user.activities.includes(:object)
As the docs say, there will be an extra query for each association. I'm not sure, but you may need to define an association the other direction for rails to know which AR models to search, eg:
class Post < ActiveRecord::Base
has_many :activities, :as => :object
end
If you're still getting an error, you might be on an earlier rails version which hadn't yet implemented this.

Its not possible to have eager loading to the Polymorphic relationship ... but u can do it for one polymorphic type like if u r having two polymorphic_type then filter the records on that type and then make eager loading it will work then .... not the perfect eager loading but partial eager loading

Related

How to specify eager loading of associations in rails model

class A < ActiveRecord::Base
has_many :Bs
has_many :Cs
...
end
I wish to load all of B's and C's whenever I do a query on A, say A.where(name: :abc), with a single query, instead of multiple calls to database.
I don't wish to specify .includes for every query I run. How do I specify eager loading in the model itself?
I looked many similar question and tried do this but it does not work:
default_scope :include => [:Bs, :Cs]
default_scope { includes(:Bs, :Cs) } should do it.
As far as I know the scope takes a block as argument not a hash of options. I just tried it in the rails console and seems to work.

What is the proper way to eager load associated objects for a 'has_many' association?

I am using Ruby on Rails 4 and I would like to eager load associated objects for a has_many association. That is, I have the following models:
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :article
end
Since I frequently retrieve comments when I load articles I would like to eager load comments each time I search for articles, for example in these cases:
# In these cases the eager loading should happen.
Article.find(1)
Article.where(:title => 'Sample text')
#current_user.articles
What is the proper way to handle this issue? How?
You should use includes method, like in following:
Article.includes(:comments).where(title: 'Sample text')
For your first case, you don't really need eager loading, since you only select one record, so this is not n+1 problem. You can just do:
article = Article.find(1)
article.comments
If you use include your articles frequently, you may define scope:
scope :with_comments, -> { includes(:comments }
and simply call it:
Article.with_comments.where(title: 'Sample text')
When your relation into which you want to include comments comes from another association, nothing changes, you can do both:
#current_user.articles.includes(:comments)
and
#current_user.articles.with_comments
If you want comments to be eager loaded automatically whenever you load article, you can use default_scope on Article model
class Article < ActiveRecord::Base
has_many :comments
default_scope :include => :comments
end

Rails Eager Load and Limit

I think I need something akin to a rails eager loaded query with a limit on it but I am having trouble finding a solution for that.
For the sake of simplicity, let us say that there will never be more than 30 Persons in the system (so Person.all is a small dataset) but each person will have upwards of 2000 comments (so Person.include(:comments) would be a large data set).
Parent association
class Person < ActiveRecord::Base
has_many :comments
end
Child association
class Comment < ActiveRecord::Base
belongs_to :person
end
I need to query for a list of Persons and include their comments, but I only need 5 of them.
I would like to do something like this:
Limited parent association
class Person < ActiveRecord::Base
has_many :comments
has_many :sample_of_comments, \
:class_name => 'Comment', :limit => 5
end
Controller
class PersonController < ApplicationController
def index
#persons = Person.include(:sample_of_comments)
end
end
Unfortunately, this article states: "If you eager load an association with a specified :limit option, it will be ignored, returning all the associated objects"
Is there any good way around this? Or am I doomed to chose between eager loading 1000s of unneeded ActiveRecord objects and an N+1 query? Also note that this is a simplified example. In the real world, I will have other associations with Person, in the same index action with the same issue as comments. (photos, articles, etc).
Regardless of what "that article" said, the issue is in SQL you can't narrow down the second sql query (of eager loading) the way you want in this scenario, purely by using a standard LIMIT
You can, however, add a new column and perform a WHERE clause instead
Change your second association to Person has_many :sample_of_comments, conditions: { is_sample: true }
Add a is_sample column to comments table
Add a Comment#before_create hook that assigns is_sample = person.sample_of_comments.count < 5

Trouble on eager loading "second degree" associated objects

I am running Ruby on Rails 3.1. I would like to eager loading "second degree" associated objects by applying some conditions, but I am in trouble.
It seems that I already solved part of my issue by using:
article_categories =
article
.categories
.includes(:comments => [:category_relationships])
.where(:category_relationships => {:user_id => #current_user.id})
where involved classes are stated as the following:
class Category < ActiveRecord::Base
has_many :comment_relationships
has_many :comments,
:through => :comment_relationships
...
end
class Comment < ActiveRecord::Base
has_many :category_relationships
has_many :categories,
:through => :category_relationships
...
end
The above code (it seems to do it right):
loads all categories by caring the has_many :through :category_relationships association (that is, by caring the .where(:category_relationships => {:user_id => #current_user.id}) condition);
eager loads all article.comments.where(:user_id => #current_user.id).
However, I would like to make some more:
to order retrieved categories by a :position attribute present in category_relationships so that the resulting article_categories are ordered by position;
to eager load also category_relationship objects where user_id == #current_user.id since the above code doesn't make that.
How can I make that by taking advantage from the eager loading?
The solution:
.order("category_relationships.position")
Imagine eager loading is cartessian product with some filtering so "where" is filtering the end result of include (left join really). But it can be done with where with subquery which first will filter categories by user then your where can be removed.

Eager-loading association count with Arel (Rails 3)

Simple task: given that an article has many comments, be able to display in a long list of articles how many comments each article has. I'm trying to work out how to preload this data with Arel.
The "Complex Aggregations" section of the README file seems to discuss that type of situation, but it doesn't exactly offer sample code, nor does it offer a way to do it in two queries instead of one joined query, which is worse for performance.
Given the following:
class Article
has_many :comments
end
class Comment
belongs_to :article
end
How can I preload for an article set how many comments each has?
Can't you use counter cache for this?
belongs_to :article, :counter_cache => true
You also need to have a migration that adds the column comments_count
You can do something nasty using SQL like:
default_scope :select => 'articles.*, (select count(comments.id) from comments where comments.article_id = articles.id) as count_comments'
and then you would have access to Article.first.count_comments.
Another (nicer) method to do it is to use the 'counter_cache' feature/option from belongs_to association.

Resources