How can I write an AR find query to have the results ordered by the number of records in a has_many association?
class User < ActiveRecord::Base
has_man
Your question doesn't make sense. The :order
parameter specifies a column name and an optional ordering direction i.e. asc(ending) or desc(ending).
What is the result that you're trying to achieve?
I would advise you not to write direct SQL, since implementations of it may vary from store to store. Fortunately, you have arel:
User.joins(:photos).group(Photo.arel_table[:user_id]).
order(Photo.arel_table[:user_id].count)
I'd add this as a comment on the top answer, but I can't for some reason. According to this post:
http://m.onkey.org/active-record-query-interface
The User.all(options)
method will be deprecated after Rails 3.0.3, and replaced with a bunch of other (handy, chainable) active record type stuff, but it makes it very hard to figure out how to put together the same kind of query.
As a result, I've gone ahead and implemented the counter cache method. This was pretty easy and painless with the exception that you need to remember to update the column information in your migration, otherwise all existing records will have "0."
Here's what I used in my migration:
class AddUserCountToCollections < ActiveRecord::Migration
def self.up
add_column :collections, :collectionusers_count, :integer, :default => 0
Collection.reset_column_information
Collection.all.each do |c|
Collection.update_counters c.id, :collectionusers_count => c.collectionusers.count
end
end
def self.down
remove_column :collections, :collectionusers_count
end
end
In theory this should be faster, too. Hope that's helpful going forward.
The easiest way to achieve this is probably to add a counter cache to that model and then sort by that column.
class Photo < ActiveRecord::Base
belongs_to :user, :counter_cache => true
end
And be sure to add a column to your users
table called photos_count
.
Then you will be able to...
User.find(:all, :order => 'photos_count')
If you don't want to add a counter cache column, your only option is to sort after the find. If you :include
the association in your find, you won't incur any additional database work.
users = User.find(:all, :include => :photos).sort_by { |u| -u.photos.size }
Note the negative sign in the sort_by
block to sort from high to low.
If you don't want an extra column, you could always ask for an extra column in the returned result set:
User.all(:select => "#{User.table_name}.*, COUNT(#{Photo.table_name}.id) number_of_photos",
:joins => :photos,
:order => "number_of_photos")
This generates the following SQL:
SELECT users.*, COUNT(photos.id) number_of_photos
FROM `users` INNER JOIN `photos` ON photos.user_id = users.id
ORDER BY number_of_photos