How to paginate multiple models with the Pagy gem in Rails? - ruby-on-rails

I'm using Searchkick with the Pagy gem to paginate my search results and it works great if I'm only searching and paginating on one model, but I can't figure out how to do this with Pagy when I need to combine multiple models into one set of Searchkick search results.
I tried using Pagy's "array extra" to combine all the the individual Searchkick model queries into an array and then loop through the results in the view, but that did not return any search results.
Here's the controller code that works for a single model:
query = params[:q].presence || "*"
results = BlogPost.pagy_search(query, suggest: true, per_page: 20)
#pagy, #results = pagy_searchkick(results, items: 10)
And in the view:
<%= pagy_nav(#pagy).html_safe %>
The following works as a combined Searchkick query across multiple models, but it's not paginated:
#search_results = Searchkick.search(query, models: [PressRelease, BlogPost, User])
How am I supposed to paginate across multiple models, then? The docs for Pagy's "Array Extra" warns:
if the data in the array comes from some DB or other persisted storage
(i.e. not some in-memory storage), then you should definitely review
your code
That's exactly what I'm trying to do. Using Pagy to paginate Searchkick results from multiple models seems like something that should be possible. If you're not supposed to use a Pagy array to do this, then how are you supposed to do this?

You can put this in the pagy initializer:
Searchkick.extend Pagy::Searchkick
Then you can use it as usual:
results = Searchkick.pagy_search(query, models: [PressRelease, BlogPost, User])
#pagy, #results = pagy_searchkick(results, items: 10)
It is not documented, so you should probably create a Documentation Issue for the missing documentation.

Related

how to rank records dynamically in a result set in rails using sunspot solr

I have users, cafes and their food_items(which have some ingredients listed). Until now i used solr to search for food_items via some ingredients that a user likes. This was accomplished using sunspot-solr search according to the sunspot docs
Also, i am able to gather a relative like-ness of a user to different cafes(based on how many times he has visited it, searched its menu etc)(this is a dynamic value that will be generated on the fly)
Problem:
I want to show the same results(food_items) fetched via solr, ranked by cafes(result re-ranking)(based on the like-ness of the user to a cafe) using sunspot solr for rails
This app is hosted on heroku and uses websolr
i have found these:
https://cwiki.apache.org/confluence/display/solr/Query+Re-Ranking
https://cwiki.apache.org/confluence/display/solr/RankQuery+API
but i have no idea as to how i can create a QParserPlugin or generate a rank query in sunspot.
sunspot provides a way to write custom queries. so if i could get help in constructing a query to fetch the like-ness and rank each record (or) any other way to implement such logic, that would be great. thanks!
you can do something like:-
def build_query(where_conditions)
condition_procs = where_conditions.map{|c| build_condition c}
Sunspot.search(table_clazz) do
condition_procs.each{|c| instance_eval &c}
paginate(:page => page, :per_page => per_page)
end
end
def build_condition(condition)
Proc.new do
# write this code as if it was inside the sunspot search block
keywords condition['words'], :fields => condition[:field].to_sym
end
end
conditions = [{words: "tasty pizza", field: "title"},
{words: "cheap", field: "description"}]
build_query conditions

Rails - Filtering Searchkick Results using a model method

I'm using Searchkick on a Rails ecommerce project. Users can search product listings.
I have some conditions under which certain products are not displayed on the site (example, if inventory = 0, etc).
Based on the searchkick docs, I have my search method in the controller as #listings = Listing.search(params[:search]). This works as intended.
In my listing model, I have the below method that defines which listings are ok to be displayed.
class Listing < ActiveRecord::Base
scope :listable, -> {
joins("INNER JOIN users
ON users.id = listings.user_id
AND users.hidelistings = 'f'") }
def self.not_expired
listable.where('(listings.updated_at >= ? or user_id = ?) and inventory > ?', Date.current - 30.day, 24, 0)
end
Based on the above, I want my searchkick method to say #listings = Listing.not_expired.search(params[:search]) but this doesn't work. How do I tell searchkick to only display results that meet the criteria?
I faced the same issue. finally, had to add relevant fields and include that filtering logic into the Searchkick query.
however, that's really good. because it is super fast just search using elastic search. or else, when data get large, run two queries in both servers is unnecessary overhead.
in my first solution, I manually pluck filtered id and passed that to elasticsearch where clause, which we hit many issues.

Searching through tag using Ransack

I'm using Ransack to perform fairly complex searches through some models. One of these models holds free text and uses the acts_as_taggable gem to tag the words.
I'm trying to create a collection selector of these words so that ransack can find any of the full text records from a subset of the tags that the user can define.
This gets me nearly there, but if I try to choose more than one word, it doesn't return any results!
= f.select :note_in, #freetexts.tag_counts_on(:tags), {}, {:multiple => true}
Ransack is not really oriented to complex searchs. It is very probable that if you stress ransack enough you end with a harder problem that if you where doing a complex select.
For complex search I would recomment Sequel, from the same author of ransack and much better oriented to complex searchs.
Moreover, according to thes thread you are on a dead end:
Ransack and acts-as-taggable-on issues
I'm not an expert at all, but this non-ransack solution could work for those that need to filter by tags with the acts-as-taggable-on gem:
#search = MyModel.ransack(params[:q])
#result = #search.result(distinct: true).includes(:related_model)
#result = #result.tagged_with(params[:tags].split(/\s*,\s*/)) if params[:tags].present?
#result = #result.paginate(page: params[:page], per_page: 20)
This expects a new :tags param that is out of the scope of Ransack. You can use to filter out the results that Ransack gives you.

Paginate data from two models into one newsfeed: Ruby on Rails 3 // Will_paginate

I'd like to make a newsfeed for the homepage of a site i'm playing around with. There are two models: Articles, and Posts. If I wanted just one in the newsfeed it would be easy:
#newsfeed_items = Article.paginate(:page => params[:page])
But I would like for the two to be both paginated into the same feed, in reverse chronological order. The default scope for the article and post model are already in that order.
How do I get the articles and posts to be combined in to the newsfeed as such?
Thanks!
EDIT: What about using SQL in the users model?
Just wondering: maybe would it be possible define in User.rb:
def feed
#some sql like (SELECT * FROM articles....)
end
Would this work at all?
in my last project i stuck into a problem, i had to paginate multiple models with single pagination in my search functionality. it should work in a way that the first model should appear first when the results of the first model a second model should continue the results and the third and so on as one single search feed, just like facebook feeds. this is the function i created to do this functionality
def multi_paginate(models, page, per_page)
WillPaginate::Collection.create(page, per_page) do |pager|
# set total entries
pager.total_entries = 0
counts = [0]
offsets = []
for model in models
pager.total_entries += model.count
counts << model.count
offset = pager.offset-(offsets[-1] || 0)
offset = offset>model.count ? model.count : offset
offsets << (offset<0 ? 0 : offset)
end
result = []
for i in 0...models.count
result += models[i].limit(pager.per_page-result.length).offset(offsets[i]).to_a
end
pager.replace(result)
end
end
try it and let me know if you have any problem with it, i also posted it as an issue to will_paginate repository, if everyone confirmed that it works correctly i'll fork and commit it to the library. https://github.com/mislav/will_paginate/issues/351
for those interested, please check this question: Creating a "feed" from multiple rails models, efficiently?
Here, Victor Piousbox provides a good, efficient solution.
Look at paginate_by_sql method. You can write unione query to fetch both articles and posts:
select 'article' as type, id from articles
union
select 'post' as type, id from posts
You can paginate both if you use AJAX. Here is well explained how to paginate using AJAX with WillPaginate.
You can paginate an array using WillPaginate::Collection.create. So you'd need to use ActiveRecord to find both sets of data and then combine them in a single array.
Then take a look at https://github.com/mislav/will_paginate/blob/master/lib/will_paginate/collection.rb for documentation on how to use the Collection to paginate any array.

Using will_paginate with multiple models (Rails)

Pretty sure that I'm missing something really simple here:
I'm trying to display a series of pages that contain instances of two different models - Profiles and Groups. I need them ordering by their name attribute. I could select all of the instances for each model, then sort and paginate them, but this feels sloppy and inefficient.
I'm using mislav-will_paginate, and was wondering if there is any better way of achieving this? Something like:
[Profile, Group].paginate(...)
would be ideal!
Good question, I ran into the same problem a couple of times. Each time, I ended it up by writing my own sql query based on sql unions (it works fine with sqlite and mysql). Then, you may use will paginate by passing the results (http://www.pathf.com/blogs/2008/06/how-to-use-will_paginate-with-non-activerecord-collectionarray/). Do not forget to perform the query to count all the rows.
Some lines of code (not tested)
my_query = "(select posts.title from posts) UNIONS (select profiles.name from profiles)"
total_entries = ActiveRecord::Base.connection.execute("select count(*) as count from (#{my_query})").first['count'].to_i
results = ActiveRecord::Base.connection.select_rows("select * from (#{my_query}) limit #{limit} offset #{offset}")
Is it overkilled ? Maybe but you've got the minimal number of queries and results are consistent.
Hope it helps.
Note: If you get the offset value from a http param, you should use sanitize_sql_for_conditions (ie: sql injection ....)
You can get close doing something like:
#profiles, #groups = [Profile, Group].map do |clazz|
clazz.paginate(:page => params[clazz.to_s.downcase + "_page"], :order => 'name')
end
That will then paginate using page parameters profile_page and group_page. You can get the will_paginate call in the view to use the correct page using:
<%= will_paginate #profiles, :page_param => 'profile_page' %>
....
<%= will_paginate #groups, :page_param => 'group_page' %>
Still, I'm not sure there's a huge benefit over setting up #groups and #profiles individually.
in my last project i stuck into a problem, i had to paginate multiple models with single pagination in my search functionality.
it should work in a way that the first model should appear first when the results of the first model a second model should continue the results and the third and so on as one single search feed, just like facebook feeds.
this is the function i created to do this functionality
def multi_paginate(models, page, per_page)
WillPaginate::Collection.create(page, per_page) do |pager|
# set total entries
pager.total_entries = 0
counts = [0]
offsets = []
for model in models
pager.total_entries += model.count
counts << model.count
offset = pager.offset-(offsets[-1] || 0)
offset = offset>model.count ? model.count : offset
offsets << (offset<0 ? 0 : offset)
end
result = []
for i in 0...models.count
result += models[i].limit(pager.per_page-result.length).offset(offsets[i]).to_a
end
pager.replace(result)
end
end
try it and let me know if you have any problem with it, i also posted it as an issue to will_paginate repository, if everyone confirmed that it works correctly i'll fork and commit it to the library.
https://github.com/mislav/will_paginate/issues/351
Have you tried displaying two different sets of results with their own paginators and update them via AJAX? It is not exactly what you want, but the result is similar.

Resources