Ruby on Rails, find top 5 Users ordered by purchases - ruby-on-rails

I have 2 models, User and Purchase.
A User has_many :purchases and a Purchase belongs_to :user
Purchases fields are:
id, product_id, user_id, amount, created_at
What I am trying to achieve is a method call such as:
User.top_five which would return the 5 Users with the highest purchase value, i.e. the sum of the purchases.amount field for each user.
I also want to be able to do something like User.top_five(:start_date=>'01/01/2010',:end_date=>'31/12/2010') , i.e. select a time period to calculate the top five users.
So I've been trying to get the right combination of joins, sums, order_bys etc, but I'm just not getting it. So hopefully someone can point me in the right direction!
Hopefully I've given enough info here, this is my first question.
thanks

I suggest something like
def self.top_five(start_date, end_date)
User.all(:select => "users.*, SUM(purchased.amount) as purchased_sum",
:joins => "LEFT JOIN purchases AS purchased ON purchased.user_id = users.id",
:group => "users.id",
:conditions => ["purchased.created_at BETWEEN ? AND ?", start_date, end_date],
:order => "purchased_sum DESC",
:limit => 5)
end

Related

How to list top 10 school with active record - rails

I have two models: School and Review.
The School model looks like this: {ID, name, city, state}
The Review model looks like this: {ID, content, score, school_id}
How do I list the top ten schools based on the score from the review model?
I thought maybe a method in the school-model, with something like this:
class School < ActiveRecord::Base
def top_schools
#top_schools = School.limit(10)
...
end
end
And then loop them in a <li> list:
<div>
<ul>
<% #top_schools.each do |school| %>
<li>school.name</li>
<%end>
</ul>
</div>
But, I dont really know how to finish the top_schools method.
You should make an average of reviews of each school.
The SQL query if you are running with MySQL should be something like:
SELECT schools.* FROM schools
JOIN reviews ON reviews.school_id=schools.id
GROUP BY schools.id
ORDER BY AVG(reviews.score) DESC
LIMIT 10
Translated in Rails:
In your School model:
scope :by_score, :joins => :reviews, :group => "schools.id", :order => "AVG(reviews.score) DESC"
In your controller:
#top_schools = School.by_score.limit(10)
The choice not to include the limitation in scope, can be more flexible and allow the display of 5 or 15.
I have only tested MySQL request. I am not sure on my rails translation.
Assuming that each school only has one review ( has_one and belongs_to), you'd want to order the reviews first and then find the corresponding school:
Review.order('score DESC').first(10).each do |r|
School.find_by_id(r.school_id)
end
I would add a new field total_score to the schools table - set to 0 by default. And then add a callback in the Review model to calculate the total score for the school when a new review is added/updated to that school.
Then do this:
School.order("total_score DESC").limit(10)
Edit: I totally missed the reviews model in that answer.
School.all(:select => "schools.*, AVG(reviews.score) as avg_score",
:joins => :reviews,
:group => 'schools.id',
:order => 'avg_score desc',
:limit => 10)
But this will get slower as you add reviews. I like the total_score answer.

How do I find all records of a model based on it's association with another model?

What I would like to find is all Events, where event.event_date is in the future, and only get the top 3 events sorted by how many Users the event has associated with it. Events and Users are joined by a HABTM relationship. Here's what I tried:
#popular_events = Event.where("event_date >= ?", Time.now)
.find(:all,
:joins => :users,
:group => 'event_id',
:order => "users.count DESC",
:limit => 10 )
I've tried a few other things with no luck. It is saying users.count is not a valid column.
believe it or not, this is a pain in the b*tt to do this with ActiveRecord. You will more or less have to rely on raw SQL to do it. Some answers here : http://www.tatvartha.com/2009/03/activerecord-group-by-count-and-joins/

Sum associated tables using Active Record

How does one sum all of the "total" columns in an association?
My SQL-fu sucks, so I would like to learn how to do so using Active Record for my rails 2.3.5 app (so no fancy syntax just yet please ;-) And I'm on MySQL.
Let's say I have:
Shop
has_many :customers
has_many :transactions, :through => :customers
So normal stuff.
shop = Shop.first
shop.transactions
=> 100
Ok, all that background for the question:
I want to SUM the total column in the transactions from the past year (Jan 1st 2010..Dec 31 2010) and display them by customer.
Whilst I know how to group transactions and find with conditions, it's the sum part where my lack of SQL lets me down.
first = Date.new(2010, 01, 01)
last = Date.new(2010, 12, 31)
shop.transactions(:conditions => {:created_at => first..last}, :group => :customer_id, :include => sum(:total))
I just took a stab, am i on the right track?
shop.transactions.sum(:total, :conditions => {:created_at => first..last}, :group => :customer_id)
This looks like an easier way. I now know that sum can take AR attributes too. cool.
Look into collect methods.
You can do things like:
transactions = Shop.transactions
total = 0
sum = transactions.collect{|i| total+=i.transaction.amount}
Replace amount with your property that contains the amount of the transaction.
You could also use .sum
sum = transactions.to_a.sum(&:amount)

How to find topics with the most flags

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.

Ruby on Rails: using nested named_scopes

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.

Resources