Eager-loading association count with Arel (Rails 3) - ruby-on-rails

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.

Related

Find records which assoicated records do not belong to certain record

In my system I have a following structure:
class Worker
has_many :worker_memberships
end
class WorkerMembership
belongs_to :worker
belongs_to :event
end
class Event
has_many :worker_memberships
end
Imagine I have a certain #event. How can I find all workers that have NO worker_memberships belonging to this #event?
This is pretty much synthesis of both other answers.
First: stick to has_many through as #TheChamp suggests. You're probably using it already, just forgot to write it, otherwise it just wouldn't work. Well, you've been warned.
I generally do my best to avoid raw SQL in my queries whatsoever. The hint about select I provided above produces a working solution, but does some unneessary stuff, such as join when there's no practical need for it. So, let's avoid poking an association. Not this time.
Here comes the reason why I prefer has_many through to has_and_belongs_to_many in many-to-many associations: we can query the join model itself without raw SQL:
WorkerMembership.select(:worker_id).where(event: #event)
It's not the result yet, but it gets us the list of worker_ids we don't want. Then we just wrap this query into a "give me all but these guys":
Worker.where.not(id: <...> )
So the final query is:
Worker.where.not(id: WorkerMembership.select(:worker_id).where(event: #event) )
And it outputs a single query (on #event with id equal to 1):
SELECT `workers`.* FROM `workers` WHERE (`workers`.`id` NOT IN (SELECT `worker_memberships`.`worker_id` FROM `worker_memberships` WHERE `worker_memberships`.`event_id` = 1))
I also give credit to #apneadiving for his solution and a hint about mysql2's explain. SQLite's explain is horrible! My solution, if I read the explain's result correctly, is as performant as #apneadiving's.
#TheChamp also provided performance costs for all answers' queries. Check out the comments for a comparison.
Since you want to set up a many to many relationship between Worker and Event, I'd suggest you use the through association.
Your resulting models would be.
class Worker
has_many :worker_memberships
has_many :events, :through => :worker_memberships
end
class WorkerMembership
belongs_to :worker
belongs_to :event
end
class Event
has_many :worker_memberships
has_many :workers, :through => :worker_memberships
end
Now you can just call #event.workers to get all the workers associated to the event.
To find all workers that don't belong to the #event you can use:
# get all the id's of workers associated to the event
#worker_ids = #event.workers.select(:id)
# get all workers except the ones belonging to the event
Worker.where.not(:id => #worker_ids)
The one-liner
Worker.where.not(:id => #event.workers.select(:id))
Try this:
Worker.where(WorkerMembership.where("workers.id = worker_memberships.worker_id").where("worker_memberships.event_i = ?", #event.id).exists.not)
Or shorter and reusable:
class WorkerMembership
belongs_to :worker
belongs_to :event
scope :event, ->(event){ where(event_id: event.id) }
end
Worker.where(WorkerMembership.where("workers.id = worker_memberships.worker_id").event(#event.id).exists.not)
(I assumed table and column names from conventions)

How do I optimize a polymorphic news feed in 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

Counting number of associations across several Rails objects

class Article
has_many :comments
end
class Comment
belongs_to :article
end
I'd like to be able to determine how many total comments exist for certain articles. For example: Article #20, #21 and #22 have a total of X comments between them.
Any pointers would be great!
I suggest this:
Comment.where(:article_id => [20, 21, 22]).count
Doing the counting etc. in the database and all in one query (which ActiveRecord will, in this case) is about as efficient as possible.
You could add a counter_cache to your articles table
class Article
has_many :comments, :counter_cache => true
end
and add a comments_count column to your articles table.
(http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html) and then sum it as so.
Article.where(:id => [20,21,22]).sum(:comments_count)
You can easily do this without a counter cache if you prefer.
Article.where(:id => [20,21,22]).joins(:comments).count

Meta_Search: Search on number of associated entries

I have the following Rails models:
class Entry < ActiveRecord::Base
has_and_belongs_to_many :tags, :uniq => true
end
And
class Tag < ActiveRecord::Base
has_and_belongs_to_many :entries, :uniq => true
end
Just so it's clear, an 'entry' can have many 'tags' associated with it.
Using the plugin Meta_Search I would like to be able to perform a search (via a form) that returns 'entries' that have more than 0 tags associated with it.
I've tried several techniques including (named) scopes and methods but I've not been able to achieve this.
Does anyone have an idea on how to perform this?
Thanks.
Something like
Entry.joins(
:tags
).select(
"entries.*, count(tags.id) as tags_count"
).order(
"tags_count DESC"
).group(
"entries.id"
).where(
"tags_count != 0"
)
This is similar to sorting by a count of associated records: Rails meta_search gem: sort by count of an associated model One of those answers recommends using counter_cache, but a comment suggests it won't work for HABTM.
Use a named scope that can select the records you are interested in (a la #mark's answer) and make it a search_method.

Hacker News rails clone: advice on modeling relationship between links, comments and votes

I'm loving Rails but we just started dating.
A user can vote on both links and comments. In addition to primary key and timestamp, I currently have the following attributes defined for these models:
Link url, headline, submitter_id, score
Comment content, commenter_id, score, link_id, parent_comment_id
Vote id, voter_id, link_id, direction
I just added the Comment Model and thinking through how to integrate it with votes. Some options:
Collapse links and comments into a single "Item" model, and map votes to the generic item_id
Have two vote tables, one for comments, one for links
Add comment_id column to existing Vote table
Not sure what's best. #1 and #3 introduce dual-purpose tables, i.e. there are certain columns in a table that are only relevant to subsets of rows within that table. #2 avoids this problem, but seems redundant and silly.
Is the tradeoff inevitable or am I not seeing the golden path? What would you recommend? And if you happen to know of a rails repository on github that handles a similar situation, I'd really appreciate a link!
I think what you are looking for is a polymorphic association. In your case would be as simple as :
class Vote < ActiveRecord::Base
belongs_to :votable, :polymorphic => true
end
class Link < ActiveRecord::Base
has_many :votes, :as => :votable
end
class Comment < ActiveRecord::Base
has_many :votes, :as => :votable
#...
end
Your votes table should look like:
id : integer
votable_id : integer
votable_type : string # Comment || Link
Here you have a Railscast about it: Polymorphic Associations Railscast

Resources