-
-
Save pawel2105/8780769 to your computer and use it in GitHub Desktop.
Given the following domain and scenario: | |
Topic has_many votes | |
A rails noob that has a massive dataset but little understanding into ActiveRecord query methods | |
and little SQL knowledge. | |
Using elegant and idiomatic Ruby, what is a good solution for returning all topics sorted by the | |
number of votes they have. Because of the SQL knowledge gap, you should avoid having to use SQL directly. |
Depends on whether you cache the association count:
# with counter_cache: true
Topic.all.sort_by{ |topic| topic.votes_count }
# with counter_cache: false
Topic.all.sort_by{ |topic| topic.votes.count }
This will return the lowest number of votes first - use reverse
to get the highest first. Obviously this may not be the most performant depending on the number of topics.
My approach:
Class Vote < ActiveRecord::Base
belongs_to :topic, dependent: destroy, counter_cache: true
end
Then use:
@topics = Topic.all.sort_by(&:votes_count)
This to me is better than using:
@sorted_topics = Topic.include(:votes).sort do |a,b|
a.votes.size <=> b.votes.size
end
The problem is that doing either of these will return an array instead of an ActiveRecord relation.
Pixeltrix
One issue I still have with this is that it's using the block to do the sorting. If you have 5 million topics and hundreds of votes each, surely you'd want the ordering to be done in the DB? I know then most people would say then you should optimize your query to use raw SQL but I feel that there is something missing that abstracts that and should belong in ActiveRecord::QueryMethods
or somewhere.
If you have 5 million topics and hundreds of votes each, surely you'd want the ordering to be done in the DB?
But if you do have 5 million rows any kind of SQL ordering sucks as well. In those circumstances I'd probably look at using a full-blown search engine.
Rails
Topic.find(:all, joins: :vote, order: 'votes.count')
Ruby
Topic.find(:all, joins: :vote).sort{ | previous, current | previous.votes.count <=> current.votes.count }