Order by score using Pagy pagination? - ruby-on-rails

I am currently trying to order some ActiveRecord records by score. In my controller i have:
def index
#pagy, #drivers = pagy(
Driver.select(
'drivers.*',
'(drivers.no_races + drivers.no_poles + drivers.no_podiums + drivers.no_wins) AS score'
).reorder('drivers.score DESC'),
page: params[:page],
items: 16
)
end
I am using Pagy for the pagination. As the code shows, I do a query to select all drivers and then add together 3 columns in the table as 'score'. I then want to order by score going from high to low and show 16 records per page.
When the page loads it seems to order by driver id. I can't see anywhere else that i have an order by, but i did add reorder to override anything else.
Anyway for whatever reason i'm stuck with the wrong ordering. Any direction is appreciated :-)
Mockup - http://29qg.hatchboxapp.com/drivers

Ok so it was a simple error in the end. "score" is ambiguous because i was using score as an alias and i also had a column in the table called score.

Related

Rails, sum the aggregate of an attribute of all instances of a model

I have a model Channel. The relating table has several column, for example clicks.
So Channel.all.sum(:clicks) gives me the sum of clicks of all channels.
In my model I have added a new method
def test
123 #this is just an example
end
So now, Channel.first.test returns 123
What I want to do is something like Channel.all.sum(:test) which sums the test value of all channels.
The error I get is that test is not a column, which of course it is not, but I hoped to till be able to build this sum.
How could I achieve this?
You could try:
Channel.all.map(&:test).sum
Where clicks is a column of the model's table, use:
Channel.sum(:clicks)
To solve your issue, you can do
Channel.all.sum(&:test)
But it would be better to try achieving it on the database layer, because processing with Ruby might be heavy for memory and efficiency.
EDIT
If you want to sum by a method which takes arguments:
Channel.all.sum { |channel| channel.test(start_date, end_date) }
What you are talking about here is two very different things:
ActiveRecord::Calculations.sum sums the values of a column in the database:
SELECT SUM("table_name"."column_name") FROM "column_name"
This is what happens if you call Channel.sum(:column_name).
ActiveSupport also extends the Enumerable module with a .sum method:
module Enumerable
def sum(identity = nil, &block)
if block_given?
map(&block).sum(identity)
else
sum = identity ? inject(identity, :+) : inject(:+)
sum || identity || 0
end
end
end
This loops though all the values in memory and adds them together.
Channel.all.sum(&:test)
Is equivalent to:
Channel.all.inject(0) { |sum, c| sum + c.test }
Using the later can lead to serious performance issues as it pulls all the data out of the database.
Alternatively you do this.
Channel.all.inject(0) {|sum,x| sum + x.test }
You can changed the 0 to whatever value you want the sum to start off at.

Repeated objects in Kaminari

I have an array of objects in my controller, paginated by Kaminari:
#pics = Pic.page(params[:page]).per(12).order(sort_column)
I render each new set of 12 pics on the same page with an AJAX request, for a manual infinite scroll effect. The sort_column bit sorts the pics according to most_viewed attribute within the object.
My problem - sometimes, when there are a number of pics with the same most_viewed attribute, - i.e. there are 10 images with "20" as their most_viewed value - I will get repeated objects when the new set of 12 is revealed with the AJAX request.
Here's an example of what I mean... I've clicked the "load more" (i've left it in to demonstrate), and there are objects repeated when the new batch loads.
Any help would be much appreciated. Thanks in advance.
You have to order on count AND another column (id would be the best), in this way SQL will returns a list that will not change (because right now SQL does not know what to do if X records have the same count).
You can change .order(sort_column) to .order("#{sort_column} ASC, id ASC"), and it should work.

How do i use .sort() to create a relation?

I am using the kaminari gem for pagination. I have a resources controller which paginates perfectly (due to the simple nature of the ordering). That can be seen here:
#resources = Resource.order("created_at desc").page(params[:page]).per(25)
That just sorts them by latest first. when i do .class it appears thats an activerecord::relation
On my tags though, I want to sort them by a relationship (the number of resources assigned to that tag)
#tags = Tag.all.sort{|a, b| b.number_of_resources <=> a.number_of_resources}.page(params[:page]).per(50)
It gives me the error however undefined methodpage' for #`
Tag.all returns an Array, hence your #page call failing, as it expects an ARel relation.
If #number_of_resources maps to a DB column, then all you need to do is:
Tag.order('number_of_resources').page(params[:page]).per(50)
If it's not, you either need to add it to the Tag database table, or just do your sort/paginate in Ruby rather than using kaminari. This will be feasible if the number of tags is under ~1000 or so.
If you do add the info to the db, check out this post: Counter Cache for a column with conditions?
you should do something like: 1) joins the two tables, 2) group rows by tag, 3) count how many rows belongs to each group, 4) order using that new column with the count
you should make a good sql statement and then you can call pagination

Will Paginate: Limit number of results

Im using the will paginate gem for ruby. I am using will paginate to get a bunch of people and sorting on a field. What I want is only the first 100 of those. Essentially the top people. I cant seeem to do this. How would i go about it? Thanks
As far as my knowledge goes will_paginate doesn't provide an option for this, I just had a root around in its source too to check, but if you don't mind having a subquery in your conditions it can be done...
people = Person.paginate(page: 10).where('people.id IN (SELECT id FROM people ORDER BY a_field LIMIT 100)').per_page(5)
people.total_pages
=> 20
Replace a_field with the field your sorting on, and this should work for you.
(note, the above uses the current version of will_paginate (3.0.2) for its syntax, but the same concept applies to older version as well using the options hash)
will_paginate gem will take total_entries parameter to limit the number of entries to paginate.
#problems = Problem.paginate(page: params[:page], per_page: 10,
total_entries: 30)
This gives you 3 pages of 10 records each.
people = Person.limit(100).paginate(:per_page => 5)

Getting the last document of limited Mongoid query result and .count()

I'm using Mongoid to work with MongoDB. Everything is fine, I like it very much and so on. In my blog application (posts controller, index action) I have this code:
#posts = Post.without(:comments)
#posts = #posts.my_search(params[:s]) if params[:s]
#posts = #posts.order_by([:created_at, :desc])
#posts = #posts.where(:pid.lt => params[:p].to_i+1) if params[:p]
#posts = #posts.limit(items_per_page+1)
The part with "where" is implementation of my own pagination method (allows to page results in one direction only, but without skip(), what I consider a plus). Now, there are few small problems that make me feel uncomfortable:
For my pagination to work I need to get the last post within that limit. But when I do #posts.last I'm getting last document of the whole query without limit. Ok, this is strange, but not a big problem. Other than that, query results act like almost-ordinary-array, so at this moment I'm getting the last element with #posts.pop (funny, but it doesn't remove any documents) or #posts.fetch(-1)
I have a feeling that this isn't "right way" and there mush be something more elegant. Also
#posts.count generates second query exactly the same as first one (without limit) but with "count" only and I don't like it.
If I make the last line look like
#posts = #posts.limit(items_per_page+1).to_ary
to convert query results into array, everything generates only one query (good), but now #posts.count stops reporting what I need (total amount of documents without limit applied) and behaves exactly like #posts.size - it returns items_per_page+1 or less (bad).
So, here are my questions:
1) What is a "correct" way to get the last document of query results within given limit?
2) How to get total amount of documents with given conditions applied without generating additional query?
UPD:
3) #posts.first generates additional query, how to prevent it and just get first document before I iterate all documents?
Getting the last document:
Post.last
Getting last document with some other queries:
Post.order_by([:created_at, :desc]).last
Getting total number documents:
Post.order_by([:created_at, :desc]).count
Recommendation: Just use the built in pagination
#posts = Post.limit(10).paginate(:page=>pararms[:page])
later:
<%= will_paginate #posts %>
Regarding the additional queries -- mongoid lazy loads everything:
#posts = Post.all #no query has been run yet
#posts.first #Ok, a query has finally been run because you are accessing the objects

Resources