I have a model called websites that has_many :likes, and of course another model called likes with belongs_to :website. I want to get an array of all the websites but order them by the number of likes a website has.
helpful info:
I have it set up so #website.likes will return the likes from #website. (#website.likes.count is the number of likes a website has)
I want to get an array of all the websites but order them by the number of likes a website has.
As others have posted, you can do a join onto likes and then order by the count. Performance may be a bit iffy depending on indexing etc. You'll have slightly different syntax depending on if you're running Rails 2 or 3.
An alternative would be to maintain a denormalised likes_count column on websites which is updated when a Like model object is saved.
Then you just need to query on Website and specify an order likes_count descending (and is easily indexed).
To do this, create a likes_count integer column on the websites and specify the :counter_cache option on the belongs_to declaration in the Likes model. e.g:
class Likes
belongs_to :website, :counter_cache => true
end
Check out http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html for more info
This query should give you what you need:
all(:select => 'websites.*, count(*) as count',
:joins => :likes,
:group => :websites,
:order => 'count DESC')
Something along the lines of:
SELECT Websites.*, COUNT(Like.ID) AS Liked
FROM websites
LEFT OUTER JOIN Like ON websites.ID = Like.website_id
GROUP BY Like.website_id ORDER BY Liked DESC
Related
Let's say I have a model called Topic. A Topic has_many :topic_flags (a TopicFlag is used to denote content that has been flagged as inappropriate). My question is this: how can I select the Topics with the most flags (limited at an arbitrary amount, descending)? I've thought about it for a while, and while I can hack together an SQL query, I'd prefer to find a more Rails way of doing it.
class Topic < ActiveRecord::Base
has_many :topic_flags
named_scope :most_flags, lambda {|min_flags| {:joins => :topic_flags,
:group => "topic_flags.topic_id",
:order => "count(topic_flags.topic_id) desc",
:having => ["count(topic_flags.topic_id) >= ?", min_flags] }}
end
This uses an inner join, so it won't pick up Topics with zero flags. You'd call it like this.
Topic.most_flags(3) # returns a sorted list of all topics with at least 3 flags.
I just got referred to stackoverflow by a friend here to help with a problem I am having. I am fairly new to ruby on rails and I am working on a collaborative project where we have a script (medal_worker.rb) that is scheduled to run at a fixed intervals to award people various medals based on various participation and success on our website. One of the new medals I am working on rewards people for "milestones". For the purpose of this problem, let's say we want to give them medals when they make 100, 1000, and 10000 comments. I would like to do this by using named_scopes from the User model (user.rb) to give me filtered lists of the users I am looking for.
My question is: How do I find the users who do not have the respective medals for the respective milestone comment level (preferably using the named_scopes from the User model)?
Here is an exerpt from my model_worker.rb file:
def award_comment_milestone(comments)
users = Users.frequent_comment_club_members(comments).not_awarded_medal(Medal.find_by_id(medal.id))
for user in users do
award_medal(medal, nil, user) if #award
end
end
Here is where I am at with the named_scopes in the user model (user.rb):
named_scope :frequent_comment_club_members, lambda { |*args|
{:include => comment_records, :conditions => ['comment_records.comment_type = ? and comment_records.comments >= ?', 'User', (args.first || 0)]}
}
named_scope :not_awarded_medal, lambda { |medal|
{:include => :awards, :conditions => ['awards.medal_id not in (select awards.medal_id from awards where awards.medal_id = ?)", medal.id] }
}
This is not working as I would like, but I don't know if the problem is in the named_scopes or how I am passing arguements or what. Thanks.
Your named_scopes look fine. Except you are starting with a single apostrophe and ending with a double apostrophe in the not_awarded_medal condition statement.
EDIT:
Take it back. Your not_awarded_medal named_scope is off.
Try something like this:
named_scope :not_awarded_medal, lambda { |medal_id|
{ :include => :awards,
:conditions => [
"? not in (select awards.id from awards where awards.user_id = users.id)", medal_id
]
}
}
untested
Now this is assuming that you have the following relationships:
User: has_many :awards
Award: belongs_to :user
If you are using has_many :through then you are going to have to change the SQL to look at the join (users_awards) table.
--
But I do see a couple of things in the award_comment_milestone function.
What is the parameter coming into award_comment_milestone? Is it an array of comments or is it a count of comments? Also where is medal defined?
If comments is an array then you need to pass comments.length into frequent_comment_club_members. If it's the count then I would rename it to comments_count so the next person can understand the logic more quickly.
Some general observations:
not_awarded_medal should just take a medal_id and not the whole object (no need to do multiple queries)
Why are you doing Medal.find_by_id(medal.id)? You already have the medal object.
I have the following model association: a student model and has_many scores.
I need to make a list showing their names and average, min, max scores. So far I am using
student.scores.average(:score) on each student, and I realise that it is doing one sql per student. How can I make the list with one joined sql?
Also how would I use that with Will_Paginate plugin?
Thank you
You want the :group and :select options to Student.find. This should work for you:
students = Student.all(
:select => "
students.*,
AVG(scores.score) as avg_score,
MIN(scores.score) as min_score,
MAX(scores.score) as max_score",
:joins => :scores
:group => 'students.id')
The calculated columns are available just like the real columns though they obviously won't be saved
students.first.avg_score
students.first.min_score
students.first.max_score
For using WillPaginate, just include your :page, :per_page, ... options and call Student.paginate instead of find. If it turns out that the pagination is getting the wrong number of pages because of the :group option, just add this: :total_entries => Student.count to your arguments
I am working on an application where I need to find the count of submitted items by users that have been referred by a user.
For Example -
User1 has referred 3 people (User2, User3, User4) and each of those users has submitted 5 articles.
I am trying to find a way to get the count of submitted items in User1's tree (should be 15 in this case).
My user model looks like the following (simplified)
class User < ActiveRecord::Base
# Code for user referrals
belongs_to :referrer, :class_name => "User"
has_many :referrals, :class_name => "User", :foreign_key => "referrer_id"
has_many :items
end
I can find out the count for each user easily (User.items.size), but I am having trouble finding a solution to get the referral counts as one sum.
Any ideas?
Try this:
user = User.find(1)
total_items_size = user.referrals.map(&:items).flatten.size
You can use select_value to manually run the SQL query:
def referred_items_count
select_value("select count(*) as referred_items
from items inner join users on users.id = items.user_id
where users.referrer_id = #{self.id};", "referred_items")
end
The benefit is that it is a lot more scalable than using Ruby to count.
Get all items of User with id of 1
total = 0
Users.find(1).referrals.each do |refer|
total += refer.items.size
end
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_many :photos
end
I want to do something like...
User.find(:all, :order => photos.count)
I realize my find is not valid code. Say I have the following data.
User 1, which has 3 photos
User 2, which has 5 photos
User 3, which has 2 photos
I want my find to bring me back the users in the order of...
User 2,
User 1,
User 3
based on the count of of the users photos
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 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
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.
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)
Counter cache will help, but you'll need an extra column in the db.
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.
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?