Rails 3 Sunspot Limiting Selected Records - ruby-on-rails-3

at the moment I'm doing a Select from multiple tables using Sunspot. I'm limiting the records selected with the condition that the creation date must have been within the past 5 days. However I would like to take this further and limit the number of records selected to say, 20? Is there any way to do this with sunspot? I'm not doing any fancy groupings or anything. Simply selecting records belonging to various models and displaying them onto the screen.
My current code:
#updates = Sunspot.search(Upload,Help, User...) do
with(:created_at).greater_than(5.days.ago)
order_by(:created_at, :desc)
end
I tried adding the 'limit' clause as specified by the readme on Github but that produced an undefined method error probably because I'm doing a global Sunspot search as opposed to a search on a specific model.
Error code:
#updates = Sunspot.search(Upload,Help, User...) do
with(:created_at).greater_than(5.days.ago)
order_by(:created_at, :desc)
limit(20)
end
Error message:
undefined method `limit' for #<Sunspot::DSL::Search:0x0000000790b8c8>

The "limit" method is for "groups". See the readme section on groups. Sunspot provides pagination (and works like a charm with the kaminari gem btw). See the section on solr pagination in the readme. Specifically, try this:
#updates = Sunspot.search(Upload,Help, User...) do
with(:created_at).greater_than(5.days.ago)
order_by(:created_at, :desc)
paginate page: 1, per_page: 20
end

Related

How can you use distinct in rails while still using ActiveRecord's

I am struggling with the following problem:
I want to have two different tabs, one that displays all recent chugs (Done), and one that displays the chugs that are the fastest per person.
However, this needs to remain an ActiveRecord, since I need to use it with link_to and gravatar, thus restraining me from group_by, as far as I understand it.
AKA: If there are three users who each have three chugs, I want to show 1 chug per user, which contains the fastest time of that particular user.
The current code looks like this, where chugs_unique should be edited:
def show
#pagy, #chugs_all_newest = pagy(#chugtype.chugs.order('created_at DESC'), items: 10, page: params[:page])
#chugs_unique = #chugtype.chugs.order('secs ASC, milis ASC, created_at DESC').uniq
breadcrumb #chugtype.name, chugtypes_path(#chugtype)
end
In this case, a chug belongs to both a chugtype and user, and the chugtype has multiple chugs.
Thanks in advance!

Why does Postgres not accept my count column?

I am building a Rails app with the following models:
# vote.rb
class Vote < ApplicationRecord
belongs_to :person
belongs_to :show
scope :fulfilled, -> { where(fulfilled: true) }
scope :unfulfilled, -> { where(fulfilled: false) }
end
# person.rb
class Person < ApplicationRecord
has_many :votes, dependent: :destroy
def self.order_by_votes(show = nil)
count = 'nullif(votes.fulfilled, true)'
count = "case when votes.show_id = #{show.id} AND NOT votes.fulfilled then 1 else null end" if show
people = left_joins(:votes).group(:id).uniq!(:group)
people = people.select("people.*, COUNT(#{count}) AS people.vote_count")
people.order('people.vote_count DESC')
end
end
The idea behind order_by_votes is to sort People by the number of unfulfilled votes, either counting all votes, or counting only votes associated with a given Show.
This seem to work fine when I test against SQLite. But when I switch to Postgres I get this error:
Error:
PeopleControllerIndexTest#test_should_get_previously_on_show:
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column people.vote_count does not exist
LINE 1: ...s"."show_id" = $1 GROUP BY "people"."id" ORDER BY people.vot...
^
If I dump the SQL using #people.to_sql, this is what I get:
SELECT people.*, COUNT(nullif(votes.fulfilled, true)) AS people.vote_count FROM "people" LEFT OUTER JOIN "votes" ON "votes"."person_id" = "people"."id" GROUP BY "people"."id" ORDER BY people.vote_count DESC
Why is this failing on Postgres but working on SQLite? And what should I be doing instead to make it work on Postgres?
(PS: I named the field people.vote_count, with a dot, so I can access it in my view without having to do another SQL query to actually view the vote count for each person in the view (not sure if this works) but I get the same error even if I name the field simply vote_count.)
(PS2: I recently added the .uniq!(:group) because of some deprecation warning for Rails 6.2, but I couldn't find any documentation for it so I am not sure I am doing it right, still the error is there without that part.)
Are you sure you're not getting a syntax error from PostgreSQL somewhere? If you do something like this:
select count(*) as t.vote_count from t ... order by t.vote_count
I get a syntax error before PostgreSQL gets to complain about there being no t.vote_count column.
No matter, the solution is to not try to put your vote_count in the people table:
people = people.select("people.*, COUNT(#{count}) AS vote_count")
...
people.order(vote_count: :desc)
You don't need it there, you'll still be able to reference the vote_count just like any "normal" column in people. Anything in the select list will appear as an accessor in the resultant model instances whether they're columns or not, they won't show up in the #inspect output (since that's generated based on the table's columns) but you call the accessor methods nonetheless.
Historically there have been quite a few AR problems (and bugs) in getting the right count by just using count on a scope, and I am not sure they are actually all gone.
That depends on the scope (AR version, relations, group, sort, uniq, etc). A defaut count call that a gem has to generically use on a scope is not a one-fit-all solution. For that known reason Pagy allows you to pass the right count to its pagy method as explained in the Pagy documentation.
Your scope might become complex and the default pagy collection.count(:all) may not get the actual count. In that case you can get the right count with some custom statement, and pass it to pagy.
#pagy, #records = pagy(collection, count: your_count)
Notice: pagy will efficiently skip its internal count query and will just use the passed :count variable.
So... just get your own calculated count and pass it to pagy, and it will not even try to use the default.
EDIT: I forgot to mention: you may want to try the pagy arel extra that:
adds specialized pagination for collections from sql databases with GROUP BY clauses, by computing the total number of results with COUNT(*) OVER ().
Thanks to all the comments and answers I have finally found a solution which I think is the best way to solve this.
First of, the issue occurred when I called pagy which tried to count my scope by appending .count(:all). This is what caused the errors. The solution was to not create a "field" in select() and use it in .order().
So here is the proper code:
def self.order_by_votes(show = nil)
count = if show
"case when votes.show_id = #{show.id} AND NOT votes.fulfilled then 1 else null end"
else
'nullif(votes.fulfilled, true)'
end
left_joins(:votes).group(:id)
.uniq!(:group)
.select("people.*, COUNT(#{count}) as vote_count")
.order(Arel.sql("COUNT(#{count}) DESC"))
end
This sorts the number of people on the number of unfulfilled votes for them, with the ability to count only votes for a given show, and it works with pagy(), and pagy_arel() which in my case is a much better fit, so the results can be properly paginated.

Rails Order by frequency of a column in another table

I have a table KmRelationship which associates Keywords and Movies
In keyword index I would like to list all keywords that appear most frequently in the KmRelationships table and only take(20)
.order doesn't seem to work no matter how I use it and where I put it and same for sort_by
It sounds relatively straight forward but i just can't seem to get it to work
Any ideas?
Assuming your KmRelationship table has keyword_id:
top_keywords = KmRelationship.select('keyword_id, count(keyword_id) as frequency').
order('frequency desc').
group('keyword_id').
take(20)
This may not look right in your console output, but that's because rails doesn't build out an object attribute for the calculated frequency column.
You can see the results like this:
top_keywords.each {|k| puts "#{k.keyword_id} : #{k.freqency}" }
To put this to good use, you can then map out your actual Keyword objects:
class Keyword < ActiveRecord::Base
# other stuff
def self.most_popular
KmRelationship.
select('keyword_id, count(keyword_id) as frequency').
order('frequency desc').
group('keyword_id').
take(20).
map(&:keyword)
end
end
And call with:
Keyword.most_popular
#posts = Post.select([:id, :title]).order("created_at desc").limit(6)
I have this listed in my controller index method which allows the the order to show the last post with a limit of 6. It might be something similar to what you are trying to do. This code actually reflects a most recent post on my home page.

How do I paginate a pre-sorted record collection using will_paginate?

How do I paginate a pre-sorted record collection using will_paginate?
Environment: Ruby 2.0.0, Rails 4.0.3, Windows 8.1, PostreSQL, Datatable 1.12.2, Will_Paginate 3.0.5
Using Railscast 340 code, I am trying to finalize my datatable implementation. I have several columns that I need to sort independently because they exist in associations and cannot be accessed directly. I've only shown one column in one direction for simplicity.
The sort is working in both the standard case and in the "product_location" case. The correct records are available in the record collection and they are in the correct order in both cases.
In the "product_location" case, when I try to paginate the result, will_paginate seems to retrieve records from the table instead of using my record collection. I end up with the wrong records in the wrong order. This action doesn't make sense to me, so I must be missing something.
The standard sort is working in both directions on all columns.
My code is as follows, and after that is the original code from Railscast 340:
def products
#products ||= fetch_products
end
def fetch_products
case sort_column
when "product_location"
products = Product.all
products.sort_by!{|product| product.readable_loc} # product_location sort
else
products = Product.order("#{sort_column} #{sort_direction}") # standard sort
end
# products is correctly sorted in both cases at this point
products = products.page(page).per_page(per_page)
# At this point, products is correctly sorted in the standard case but not in the "product_location" case
# In "product_location" case, pagination seems to be accessing the table directly again, not using the record collection?
if params[:sSearch].present?
products = products.where("stock_number like :search", search: "%#{params[:sSearch]}%")
end
products
end
The original code is:
def products
#products ||= fetch_products
end
def fetch_products
products = Product.order("#{sort_column} #{sort_direction}")
products = products.page(page).per_page(per_page)
if params[:sSearch].present?
products = products.where("name like :search or category like :search", search: "%#{params[:sSearch]}%")
end
products
end
It is because Product.all returns instance of ActiveRecord::Relation and products.sort_by! fetches all records from database a sort them with ruby. Than if you call products = products.page(page).per_page(per_page) it creates new sql query with new limit and offset to fetch records on given page.
So if you want to use will_paginate for pagination, you cannot use .sort_by! method for data sorting, but you have to order records with Products.order which will sort records via SQL in the database.

will_paginate generates wrong number of page links

I am Using will paginate 3.0.2 and Rails 3.1.0.
The following code lives within my controller.
#users = User.visible_for(current_user).
includes(:study_courses).
ordered_by_last_name.
page(params[:page]).per_page(20)
In a partial where #users has been assigned with users from above I do:
= will_paginate users, previous_label: h("<"), next_label: h(">")
If there are 20 Users it gives me 6 page links, where the first page contains 20 users, the second page contains 10 users and of course the remaining pages contain zero users.
I can not figure out why there are 6 page links generated instead of 3.
UPDATE:
Figured out that will_paginate does not use distinct to count the records. Any ideas how to do this?
OK, I found a solution on my own:
user_count = User.visible_for(current_user).count(distinct: true)
#users = User.visible_for(current_user).
includes(:study_courses).
ordered_by_last_name.
paginate(page: params[:page],
per_page: 20,
total_entries: user_count)
In my scope I use disctinct, but calling count on the relation seems to overwrite that. So one has to count by hand and pass the count into the paginate method.
This way looks cleaner: https://stackoverflow.com/a/10740732/2599681
I tried it and works nicely with Rails 3.2.11 and WillPaginate 3.0.4.
Regards!
In my case, .count gives wrong number, too. Because .count runs SQL whitch includes "distinct". I changed it to .length and it works fine.
Like this
user_count = User.some_scopes.length
#users = User.some_scopes.paginate(page: params[:page],
per_page: 20,
total_entries: user_count)