I'm creating a search page that will do an application wide search on users, posts, and comments. I currently have:
# POST /search
def index
query = params[:query]
#users = User.search(query).page(params[:page])
#posts = Post.search(query).page(params[:page])
#comments = Comment.search(query).page(params[:page])
respond_to do |format|
format.html
end
end
However I'm really trying to get something where all the results are mixed together then paginated. What are some of the strategies for doing paginated search like this? Thanks!
Ever since this commit: https://github.com/amatsuda/kaminari/commit/f9f529fb68ab89feea38773a4c625c1b14859128
You can do the following
In your view you can do this:
<%= paginate #users, :remote => true, :param_name => "user_page" %>
<%= paginate #posts, :remote => true, :param_name => "post_page" %>
<%= paginate #comments, :remote => true, :param_name => "comment_#{some_post_id}_page" %>
and then in your controller you can refer to them in this way:
#users = User.search(query).page(params[:user_page])
#posts = Post.search(query).page(params[:post_page])
#comments = Comment.search(query).page(params[:comment_page])
and your view's js.erb you might have something like:
$('#posts').html('<%= escape_javascript render(#posts) %>');
$('.table-pager').html('<%= escape_javascript(paginate(#posts, :remote => true).to_s) %>');
Before thinking about a solution, you need to first define exactly what you want the final result to be. If you want to display a few of each type of record on the results page you can modify the approach you posted and combine the three paginated results using:
#results = #users + #posts + #comments
#results.sort! { |a, b| a.score(query) > b.score(query) }
Each object will need to have an instance method 'score' that will let it sort based on the query priority. Also, you will need to modify your view to handle correct rendering of each item and ensure that the pagination is called on the model with the most pages.
Alternatively, a more robust method would be to add a full-text search service (such as Index Tank, Web Solr, Thinking Sphinx). The technology for what's hot for these moves quickly, so do some research and find one that fits your needs. Example syntax for this would be something like:
User.multi_solr_search query, models: [Post, Comment]
You could combine the results from the query and run page on that.
users = User.search(query)
posts = Post.search(query)
comments = Comment.search(query)
#results = users + posts + comments
#results.page(params[:page])
Related
Quick question.
I'm trying to use the Acts-As-Taggable gem to search for multiple tags at the same time. The trick is, I want to search for any posts containing at least one of the tags. I've partially figured it out.
If in my songs controller I put
if params[:tag]
#songs = Song.tagged_with(["Hip-Hop-3, Hip-Hop-5"], :any => true)
elsif...
It will find all posts containing one or more of the tags.
The problem is, when I want to move these parameters into a ERB link_to
Here is my controller:
if params[:tag]
#songs = Song.tagged_with(params[:tag], :any => true).order("created_at DESC").paginate(:page => params[:page], :per_page => 36)
elsif
Here is my link_to
<%= link_to "House", tag_path(["Hip-Hop-5","Hip-Hop-3"]) %>
In this situation the URL is the same, http://localhost:3000/tags/Hip-Hop-5/Hip-Hop-3, but it doesn't show any of the posts like the previous method did.
Am I doing something wrong with the parameter?
OR
Is there a way I can have the tagged_with method search for tags containing certain characters. So find tags that contain the word "Hip-Hop" in it? Thank you!
Thank you,
Matt
You need to modify the url as follows:
<%= link_to "House", tag_path(tag: ["Hip-Hop-5","Hip-Hop-3"]) %>
I believe that the controller won't be able to find params[:tag] as per your link because it is not passed as hash to the method tag_path, using tag: params[:tag] should ensure correct assignment of the params[:tag] to the parameters you were passing.
I have a simple Rails app which has an index page that shows 59 posts.
The problem is that if I search for something, Post.count continue to show me the original number of Posts on the index page - for example if I search for a post called 'Quilon', I get a search result showing just 1 post, but the Post.count still shows the original post number which is 59.
How do I fix this?
INDEX.HTML.ERB CODE
<%= Post.count %>
SEARCH FUNCTION IN POSTS CONTROLLER
def index
#posts = Post.all
if params[:search]
#posts = #posts.search(params[:search]).order("created_at DESC")
end
if params[:zagat_status].present?
#posts = #posts.zagat_status(params[:zagat_status]).order("created_at DESC")
end
end
In your index action you narrow down the posts using some conditions, so you should operate on #posts variable, not on the model Post itself.
Which means you want to use #posts.count.
You should give <%= #posts.count %> instead of <%= Post.count %>. <%= Post.count %> returns count of all the records. It is just same as
select count(*) from "posts"
so the conditions are ignored.
Use #posts.count instead of Post.count
I have a blogging application in which User has_many posts. I am using pagination with Booststrap. How can I make the partial_count method work with pagination? Currently, the count resets on every page instead of carrying over across pages.
posts_controller.rb
def index
#posts = Post.order("created_at desc").paginate(page: params[:page], :per_page => 12)
end
views/posts/index.html.erb
<%= render #posts %>
<%= will_paginate %>
views/posts/_post.html.erb
<%= post_counter +1%>
<%= post.name %>
The counter works fine on the first page. However, all subsequent pages also start with "1". How can I make subsequent pages start with (number of pages * 12 + 1) instead?
Thanks for your feedback!
Use #posts.offset to get the proper counter initialisation.
I'm using the will_paginate gem. The default is 30 elements per page. How do I customize this?
If your controller is called User, you can do something like this in your controller:
#users = User.paginate :page => params[:page], :per_page => 10, :order => 'name ASC' This will show 10 results per page.
In your view:
<%= will_paginate #users %>
See the per_page option here:
https://github.com/mislav/will_paginate/wiki
It will allow you to change the number displayed per page, for anytime that model is paginated.
For a controller/action specific approach see Raunak's answer.
I'm implementing search and am having some difficulties with my sql/finding.
Basically, I have a favorites page, that gets a collection of favorites with:
#favorites = current_user.votes
In the view, I then loop through my favorites, and can call .voteable on them to get the actual object that was voted on. This is making search very difficult for me to write.
I was wondering if it was possible to change my original collection, so that I'm actually getting the .voteable objects each time to dry up my view/help me write my search. I cannot called current_user.votes.voteables but individually can do something like current_user.votes.first.voteable
What I've attempted is a loop like so:
#favorites = current_user.votes.each {|vote| vote.voteable }
Which is wrong, and I'm just getting my votes again, and not the actual voteable object. I was just wondering if there was a way to get these voteables from looping through my votes like this.
Any pointers would help, thanks.
EDIT:
Expansion what I mean by search:
I'm building a method in the model that searches self, here is an example:
def self.search(search)
if search
where('title LIKE ?', "%#{search}%")
else
scoped
end
end
I pass in search from the view, with a form like:
<div id="search_form">
<%= form_tag topic_links_path, :method => 'get', :id => "links_search" do %>
<%= text_field_tag :search, params[:search], :size => "35" %>
<%= submit_tag "Search", :name => nil %>
<% end %>
</div>
That way, when I make my collection in the controller, I can call .search(params[:search]) to get only the items that are like whatever the user entered in the form. I'm using the vote_fu gem for handling the votes/voteables.
You need to use map instead of each:
#favorites = current_user.votes.map {|vote| vote.voteable }
each simply loops through the elements and performs the operation on them, but it doesn't return the result in an array format. That's what map does.
On a side note, you can use a scope for search instead of a function. It might be a little cleaner:
scope :search, lambda{ |title| where('title LIKE ?', "%#{title}%") unless title.blank? }