Rails 3: select object with the highest rating? - ruby-on-rails

The model Article has an attribute :rating. This attribute is updated everytime someone rates the article and the average rating is calculated and stored in the database.
Now I want to get the Article with the highes rating. But how?

#article = Article.order('rating desc').limit(1)

Either of those two will do the job, depending on which style you prefer:
#article = Article.find(:first, :order => 'rating DESC')
#article = Article.order('rating DESC').first
Edit:
If you already have an array #articles, and you have a good reason why you can't or shouldn't run a separate SQL query, you can do something like this:
#top_article = #articles.sort { |a1,a2| a2[:rating] <=> a1[:rating] }.first
Obsolete:
I wonder though, how you can compute the average rating after every new vote, if you only save the average in the database? If the current average rating is 5, and I submit a new rating 10, the number of ratings must be known in order to calculate the new average. How this is done may be relevant for the solution to your problem.

Related

Calculate average conditionally based on an attribute in a rake task (Rails)

I have a requirement where I need to calculate the average of units sold for a product based on the company they were sold at.
The scenario is we're importing data from a legacy database, and when importing I'd like to perform some calculations based on the difference between units sold for new item and the average of the existing item's, when they were sold at the same company.
The model is called Product and has attributes of:
name
interest (How many units were sold)
company (What company they
were sold at)
Now previously, I am able to calculate the average of each company on the model like so:
def self.average_interest(company)
where(company: company).average(:interest)
end
But now I am trying to do the calculation on a rake task.
Here's what I came up with and it's not working:
#company = u.Company
#u.Company is the field name from the legacy database
def average_interest
Product.average(:interest, :conditions => ['company = ?', #company])
end
Any ideas?
Thanks!
EDIT:
Have updated it from '#company' to #company however the value being returned is still incorrect
In your rake task you can pass in the variable, so something like this:
def average_interest(company)
Product.average(:interest, :conditions => ['company = ?', company])
end
unentered_legacy_companies.each do |u|
average_interest(u)
end
after playing around, looks like it was only a slight adjustment from the original code that was needed.
Just had to add the model (Product) to the query in the code:
def average_interest(company)
Product.where(company: company).average(:interest)
end
And then I am storing it in a variable like so:
#company_average = average_interest(u.Company)

Rails best way to get previous and next active record object

I need to get the previous and next active record objects with Rails. I did it, but don't know if it's the right way to do that.
What I've got:
Controller:
#product = Product.friendly.find(params[:id])
order_list = Product.select(:id).all.map(&:id)
current_position = order_list.index(#product.id)
#previous_product = #collection.products.find(order_list[current_position - 1]) if order_list[current_position - 1]
#next_product = #collection.products.find(order_list[current_position + 1]) if order_list[current_position + 1]
#previous_product ||= Product.last
#next_product ||= Product.first
product_model.rb
default_scope -> {order(:product_sub_group_id => :asc, :id => :asc)}
So, the problem here is that I need to go to my database and get all this ids to know who is the previous and the next.
Tried to use the gem order_query, but it did not work for me and I noted that it goes to the database and fetch all the records in that order, so, that's why I did the same but getting only the ids.
All the solutions that I found was with simple order querys. Order by id or something like a priority field.
Write these methods in your Product model:
class Product
def next
self.class.where("id > ?", id).first
end
def previous
self.class.where("id < ?", id).last
end
end
Now you can do in your controller:
#product = Product.friendly.find(params[:id])
#previous_product = #product.next
#next_product = #product.previous
Please try it, but its not tested.
Thanks
I think it would be faster to do it with only two SQL requests, that only select two rows (and not the entire table). Considering that your default order is sorted by id (otherwise, force the sorting by id) :
#previous_product = Product.where('id < ?', params[:id]).last
#next_product = Product.where('id > ?', params[:id]).first
If the product is the last, then #next_product will be nil, and if it is the first, then, #previous_product will be nil.
There's no easy out-of-the-box solution.
A little dirty, but working way is carefully sorting out what conditions are there for finding next and previous items. With id it's quite easy, since all ids are different, and Rails Guy's answer describes just that: in next for a known id pick a first entry with a larger id (if results are ordered by id, as per defaults). More than that - his answer hints to place next and previous into the model class. Do so.
If there are multiple order criteria, things get complicated. Say, we have a set of rows sorted by group parameter first (which can possibly have equal values on different rows) and then by id (which id different everywhere, guaranteed). Results are ordered by group and then by id (both ascending), so we can possibly encounter two situations of getting the next element, it's the first from the list that has elements, that (so many that):
have the same group and a larger id
have a larger group
Same with previous element: you need the last one from the list
have the same group and a smaller id
have a smaller group
Those fetch all next and previous entries respectively. If you need only one, use Rails' first and last (as suggested by Rails Guy) or limit(1) (and be wary of the asc/desc ordering).
This is what order_query does. Please try the latest version, I can help if it doesn't work for you:
class Product < ActiveRecord::Base
order_query :my_order,
[:product_sub_group_id, :asc],
[:id, :asc]
default_scope -> { my_order }
end
#product.my_order(#collection.products).next
#collection.products.my_order_at(#product).next
This runs one query loading only the next record. Read more on Github.

How do you get the "ranking" of an integer attribute in Rails?

I have an Article model with a view_count attribute.
I want to create a rank method in the Article model that returns the rank of the article. Ie. highest view_count gets rank: 1.
How would I do this? My first instinct was to query for Article.all and write some ruby code to do this. Is there a more efficient way of doing this via queries?
Not particularly efficient, but something like
def rank
(Article.where('view_count > ?', self.view_count).count) + 1
end
You can order the elements while getting them from the Databse
Article.order(:view_count) maybe with a limit Article.order(:view_count).limit(10)
If you really need to store a rank you could now do something like:
top_ten = Article.order(:view_count).limit(10)
top_ten.each_with_index.do |article,i|
article.rank = i
article.save
end
But you would have to update this every time any view_countchanges so it should be better to just
store the view_count and display the rank while you generate the list.

Rails sort users by method

I'm trying to rank my user's in order of an integer. The integer I'm getting is in my User Model.
def rating_number
Impression.where("impressionable_id = ?", self).count
end
This gives each User on the site a number (in integer form). Now, on the homepage, I want to show an ordered list that places these user's in order with the user with the highest number first and lowest number second. How can I accomplish this in the controller???
#users = User....???
Any help would be appreciated!
UPDATE
Using this in the controller
#users = User.all.map(&:rating_number)
and this for the view
<% #users.each do |user| %>
<li><%= user %></li>
<% end %>
shows the user's count. Unfortunately, the variable user is acting as the integer not the user, so attaching user.name doesn't work. Also, the list isn't in order based on the integer..
The advice here is still all kinds of wrong; all other answers will perform terribly. Trying to do this via a nested select count(*) is almost as bad an idea as using User.all and sorting in memory.
The correct way to do this if you want it to work on a reasonably large data set is to use counter caches and stop trying to order by the count of a related record.
Add a rating_number column to the users table, and make sure it has an index defined on it
Add a counter cache to your belongs_to:
class Impression < ActiveRecord::Base
belongs_to :user, counter_cache: :rating_number
end
Now creating/deleting impressions will modify the associated user's rating_number.
Order your results by rating_number, dead simple:
User.order(:rating_number)
The advice here is just all kinds of wrong. First of model your associations correctly. Secondly you dont ever want to do User.all and then sort it in-memory based on anything. How do you think it will perform with lots of records?
What you want to do is query your user rows and sort them based on a subquery that counts impressions for that user.
User.order("(SELECT COUNT(impressions.id) FROM impressions WHERE impressionable_id = users.id) DESC")
While this is not terribly efficient, it is still much more efficient than operating with data sets in memory. The next step is to cache the impressions count on the user itself (a la counter cache), and then use that for sorting.
It just pains me that doing User.all is the first suggestion...
If impressions is a column in your users table, you can do
User.order('impressions desc')
Edit
Since it's not a column in your users table, you can do this:
User.all.each(&:rating_number).sort {|x,y| y <=> x }
Edit
Sorry, you want this:
User.all.sort { |x, y| y.rating_number <=> x.rating_number }

How to Average Multiple Columns in Rails

I have the following objects: Products, Ratings, and Users. I am trying to filter and select a number of Products that the User owns (through a has_many :through relationship with UserProducts) and average a certain column the Ratings table that matches their User ID and the correct Product ID.
So, my function is something along these lines:
def find_rating(criteria)
product = self.products.find(:all, :conditions => ["criteria = ?", criteria])
rating = self.ratings.where("product_id = ?", product).average(:overall)
end
I think that I'm going about this the wrong way, because I'm trying to find a product_id by passing an entire array of data consisting of multiple products. But, I think of using a more traditional loop and that seems convoluted. Can someone point me in the right direction for solving this problem? Thanks!
If product is a single entry, as it appears to be in your code, I would do this:
rating = self.products.find_by_criteria(criteria).ratings.average(:overall)
If it's an array of products, this method may help you: http://apidock.com/rails/ActiveRecord/Batches/ClassMethods/find_each

Resources