Custom Pagination with Sort, Rails 3 - ruby-on-rails

I have a rails 3 app that displays user submissions in order of how many votes they have. I have a model method called "rank" that calculates the score for each submission. Currently, when listing all the submissions I am using the following in my submissions_controller.rb
def index
#submissions = Submission.all.sort_by(&:rank).reverse
end
However, I want to add pagination to this, and it seems that neither 'will_paginate' or 'kamninari' will work properly here. This is because I need to sort the database columns by rank before paginating. Is there a better way to phrase my query so that pagination could be created with one of these gems, or do you know of a good custom pagination solution?
Thanks!

This ended up working:
#submissions = Submission.all.sort_by(&:rank).reverse
#submissions = Kaminari.paginate_array(#submissions).page(params[:page]).per(25)

Perhaps you could calculate rank in SQL? Or, add a column and save it with the record (e.g. before_save :calculate_rank).

Related

Rails 5 get specific field/value from table

I have a table called group. I want this method to return just the content of the relevant record's ID field. At the moment it returns an active record object ID.
def get_group_name(group_id)
Group.select([:name]).where("id = ?", group_id)
end
Thanks in advance.
I think you can do easier with find
def get_group_name(group_id)
Group.find(group_id).name
end
This will get you only the name of the group.
def get_group_name(group_id)
Group.where(id: group_id).limit(1).pluck(:name).first
end
It will run this query:
SELECT name
FROM groups
WHERE id = ?
LIMIT 1;
A side note is, be careful of what you’re doing. Any time you have a method to get a single field’s value, while it can be more efficient at times, it can easily be misused. If you’re looping over a collection of group ids trying to grab all of the names, then you’d be better off 1 query up front for all of the names as opposed to 1 per group id on the page. So just keep and eye on your console and pay attention to the queries you’re running.
Also, if you are looking over a collection, you may want to look into includes for your ActiveRecord queries, to include the group data in the previous query. You can benchmark this all to figure out what’s fastest for your use case.

User's position in Ruby on Rails, Devise gem

I want to display a user's position on show page. For example, "Your position is 1921 out of 195119". Is there any effective way to do such thing in devise gem?
My idea was to add an extra column inside users table and make it auto increment for each entry. However, I think that there is a better way to do so. Thank you very much for your help and time.
Your User models have number of records and those are stored with id which is already auto incremented. Adding position similar to id is useless.
You can achieve it by following as few of your records used to get deleted and you want position as per existing records.
#position = User.all.ids.sort.index(#user.id)
Here #user is object for which 'show' action is getting called to get #position
If you have a column for position you probably need to update it if some of the records are deleted.
You can do two count queries, given a user object.
count_1 = User.where("id <= ?", user.id).count
count_1 is the position of the user
count_2 = User.count
count_2 is the total postions

Rails: Order custom model using custom method

I have a custom model called product, and it has many reviews.
i have a method that calculates the review
def rating
total = 0
reviews_count = reviews.count
return 0 if reviews_count == 0
reviews.each do |review|
total += review.grade
end
total.to_f/reviews_count
end
i would like to know how could i use this method to Order my products.
At products_controller.rb, if i use:
#products = Product.all.order("price")
its easy, it gives me the products list ordered by price. But, if i use, for example:
#products = Product.all.sort_by{|p| p.rating}
it gives me an array and not a "ActiveRecord::Relation"
I would like to know how could i order my product using a custom method that returns a value.
In general, you can't. Ordering happens in your database, which has no knowledge about any method you typed in your application. What you need is a way of translating your method into a valid sql. In your case, you can do:
Product.joins(:reviews).group('products.id').order('AVG(reviews.grade)')
That will give you sorted results and the relation object. However, relations with join are not that nice to work with, especially if you try to add another join. Also this might get quite slow when your database grows.
What you're doing in your example is running the query then using sort_by to sort the result set.
If you want to get back an activerecord collection instead of an array, and potentially chain this with other scopes, you should move the logic from your method into SQL, and put it in a scope.

ActiveRecord return the newest record per user (unique)

I've got a User model and a Card model. User has many Cards, so card has a attribute user_id.
I want to fetch the newest single Card for each user. I've been able to do this:
Card.all.order(:user_id, :created_at)
# => gives me all the Cards, sorted by user_id then by created_at
This gets me half way there, and I could certainly iterate through these rows and grab the first one per user. But this smells really bad to me as I'd be doing a lot of this using Arrays in Ruby.
I can also do this:
Card.select('user_id, max(created_at)').group('user_id')
# => gives me user_id and created_at
...but I only get back user_ids and created_at timestamps. I can't select any other columns (including id) so what I'm getting back is worthless. I also don't understand why PG won't let me select more columns than above without putting them in the group_by or an aggregate function.
I'd prefer to find a way to get what I want using only ActiveRecord. I'm also willing to write this query in raw SQL but that's if I can't get it done with AR. BTW, I'm using a Postgres DB, which limits some of my options.
Thanks guys.
We join the cards table on itself, ON
a) first.id != second.id
b) first.user_id = second.user_id
c) first.created_at < second.created_at
Card.joins("LEFT JOIN cards AS c ON cards.id != c.id AND c.user_id = cards.user_id AND cards.created_at < c.created_at").where('c.id IS NULL')
This is a bit late, but I am working on the same matter, and i found this one works for me :
Card.all.group_by(&:user_id).map{|s| s.last.last}
What do you think ?
I've found one solution that is suboptimal performance-wise but will work for very small datasets, when time is short or it's a hobby project:
Card.all.order(:user_id, :created_at).to_a.uniq(&:user_id)
This takes the AR:Relation results, casts them into a Ruby Array, then performs a Array#uniq on the results with a Proc. After some brief testing it appears #uniq will preserve order, so as long as everything is in order before using uniq you should be good.
The feature is time sensitive so I'm going to use this for now, but I will be looking at something in raw SQL following #Gene's response and link.

Sort by ranking algorithm using will-paginate

I'm creating a digg-like site using Ruby on Rails that ranks the item (based on this algorithm). I'm using the will-paginate gem list the items in pages.
The problem is, will-paginate only allows me to insert ':order =>' based on the table data. I would like to make will-paginate to sort by a number which is calculated using a function based on different fields on the table (e.g number of votes, age hours).
How can I do that?
According to the comments/documentation in the code, you can use
Post.paginate_by_something
in order to use
Post.find_all_by_something
to get the collection of items you want to use paginate for. The :order option is required to set the order of the items.
So for your problem you can create a custom find method, adding a rank column using your algorithm. Use the :order option to sort on this ranking.
You can also use the paginate method on named scopes. If you make a scope for the ordering you could just do Article.ordered.paginate.

Resources