count and retrieve objects of polymorphic association - ruby-on-rails

So i've got three models
"Text" has_many :statistics, :as => :loggable
"Document" has_many :statistics, :as => :loggable
"Statistic" belongs_to :loggable, :polymorphic => true
"Company" has_many "Documents"/"Texts"
now i want to get different restults, f.e.
get the total count of statistic objects of all text/document objects of a company
get the total count of statistic objects of all text/document objects of a company
this month
get the first five Texts/Documents of a company with the most statistic
objects
get the first five Texts/Documents of a company with the most statistic
objects this month
this month is related to the creation date of the statistic object.
i really have no idea how to achieve this. i tried different things in the rails console but without luck.
company.texts
company.documents
any ideas how to do this?
thanks in advance. please leave a comment if something is unclear.

The first 2 are pretty simple.
get the total count of statistic objects of all text/document objects
Statistic.where(:loggable_type => "Text").where(:loggable_id => company.texts)
get the total count of statistic objects of all text/document objects this month
Statistic.where('created_at between ? and ?', 1.month.ago, Time.now).where(:loggable_type => "Text").where(:loggable_id => d.texts).count
get the first five Texts/Documents of a company with the most statistic objects
ids = Statistic.select("COUNT(*) AS count_all").where(:loggable_type => "Text").where(:loggable_id => company.texts).group(:loggable_id).order("count_all desc").limit(5).size
Text.where(:id => ids)
get the first five Texts/Documents of a company with the most statistic objects this month
ids = Statistic.select("COUNT(*) AS count_all").where('created_at between ? and ?', 1.month.ago, Time.now).where(:loggable_type => "Text").where(:loggable_id => d.texts).group(:loggable_id).order("count_all desc").limit(5).size
Text.where(:id => ids)

Try it with
company.texts.join(:statistics).where('statistics.created_at between ? and ?', Time.now, 1.month.ago)
and
company.documents.join(:statistics).where('statistics.created_at between ? and ?', Time.now, 1.month.ago)

Related

Advanced activerecord query where association with a limit

I'm trying to do a more advanced query with activerecord and can't figure out the best way to write it. Hopefully this makes sense...
My POSTGRES/rails app has models:
Company has_many :employees
Employee has_many :evaluations
Evaluations has decimal column :score. I want to get the 10 lowest evaluation scores for a company by name. BUT I also only want to pull a SINGLE lowest score from each employee. I don't want to have 5 of the 10 scores in my results list all coming from the same employee ID.
I have:
#list = Company.where(:name => 'Google').joins(:evaluations)
But I am struggling with how to limit the evaluations to a single low value on a single employee.id
What is the best way to do this?
Here's the 'off-the-top-of-my-head' guess:
Employee.
select('employees.*, min(evaluations.score) as lowest_score').
joins(:company, :evaluations).
where(:company => {:name => 'Google'}).
group('employees.id').
order('lowest_score').
limit(10)
#list = Evaluation.joins(:employee => :company)
.where(:company => { :name => "Google" })
.order(:score)
.group(:employee => :id)
.first(10)
IIRC, ordering by score should mean you don't have to bother with min functions and stuff as it will pick the first record when doing the grouping, which will be the lowest score. Give it a whirl.

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)

RoR top five ratings

I have a model that has ratings in it for an post.
I want to display a list of the top 5 ratings for a given post, but I am completely lost on where to start. I figure the find method might have something useful, but I'm unsure. I've even considered looping through each record getting its size, adding it to a hash, sorting the hash, etc., but that seems too complicated.
Does anyone know how I might accomplish something like this?
Thank you
Edit: I found this to get all the posts that have the rating of agree:
Post.find(:all, :include => :post_ratings, :condtions => ['post_ratings.agree = ?', true])
The only problem is I can't figure out how to get the top five ratings from this query.
Might be worth giving a little more of an example of the code you're working with but I'll answer with a few assumptions.
If you have:
class Post
has_many :post_ratings
end
class PostRating
belongs_to :post
# Has a 'rating' attribute
end
You can find the top five post ratings with:
p = Post.find(:first) # For example
p.post_ratings.find(:all, :limit => 5, :order => 'rating desc')
To get the top five post ratings overall you can do:
PostRating.find(:all, :limit => 5, :order => 'rating desc')
UPDATE:
Following your edit it seems you have an 'agree' and a 'disagree' column. Not sure how that works in combination so I'll stick with the 'agree' column. What you'll need to do is count the ratings with agree flagged. Something like:
count_hsh PostRating.count(:group => 'post_id',
:order => 'count(*) desc',
:conditions => { :agree => true },
:limit => 5)
This will return you a hash mapping the post id to the count of agree ratings. You can then use that post_id to locate the posts themselves. The ratings are provided by the counts so the individual ratings are (I think) of no use though you could access them by calling post.post_ratings.
So, to get the top five posts:
#top_five_posts = []
count_hsh.each_pair do |post_id, ratings|
p = Post.find(post_id)
p[:rating_count] = ratings
#top_five_posts << p
end
This is probably more verbose than it could be but is hopefully illustrative. The p[:rating_count] is a virtual attribute which isn't in the database but will allow you to access the .rating_count method on the posts in your view if you wish to.
Assuming the same Post and PostRating from Shadwell's answer:
class Post
has_many :post_ratings
end
class PostRating
belongs_to :post
# Has a 'rating' attribute
end
To get the top five ratings for all Posts:
post_ratings.find(:all, :limit => 5, :order => 'rating desc')
To get the top five ratings for a specific Post you can:
p = Post.find(:first)
post_ratings.find_all_by_post_id(p.id, :limit => 5, :order => 'rating desc')
To find all posts sorted by average rating, you can use ActiveRecord::Calculations.
PostRating.average(:rating, :group => :post_id, :include => :post, :order => 'average desc')

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