Using shared indexes with acts_as_ferret
So by now we all know how to do wicked-cool search with acts_as_ferret. (If not, the RailsEnvy guys can lend a hand, but the tutorial is a little outdated for the latest trunk version of acts_as_ferret. Just replace #find_by_contents with #find_with_ferret and you should be good.)
But searching a single model is so last year. All the cool kids are getting promiscuous with their searches and involving multiple models. Fortunately for us, recent revisions of acts_as_ferret makes this easy-peasy.
The key to making this happen is a shared index: we want all of our models indexed in one place so ferret only has to do one search. We can do it without a shared index, but then we have to do a ferret search and thus a SQL select for each model. Plus, if we want your search results interspersed and sorted by rank, we have to have a shared index.
Enough chit-chat, show us how!
Ok, I’m getting to it. Grab the latest version of acts as ferret from trunk:
script/plugin install svn://projects.jkraemer.net/acts_as_ferret/trunk/plugin/acts_as_ferret
Now, instead of defining acts_as_ferret in our models, we define them all in config/aaf.rb
ActsAsFerret::define_index('shared',
:models => {
Person => {:fields => [:first_name, :last_name, :phone, :bio]},
Company => {:fields => [:name, :description]},
Post => {:fields => [:title, :body]}
},
:ferret => {
:default_fields => [:first_name, :last_name, :phone, :bio, :name, :description, :title, :body]
}
)
This defines a new index, called “shared”, and then defines the acts_as_ferret configuration for each model.
Now for the fun part: searching our shiny new index.
def search
@results = ActsAsFerret.find(params[:q], 'shared')
end
This will give us one array with any models that matched the search query, ordered by rank. And for those times when we only want to search one model, we can still do that.
def search
@people = Person.find_with_ferret(params[:q])
end
How do we display the results?
Update: Sorry, originally I had an example using resources, but that doesn’t work as-is; I was doing something a little different in the app that this example came from.
To display our search results, we just render a partial for each model in the result:
<% @results.each do |result| %>
<%= render :partial => "search/#{dom_class(result)}" %>
<% end %>
This will just look for a partial for each model (like search/_person.html.erb).
So there you have it. Now you too can have promiscuous searching.
Update: I’ve put together an example rails app that uses the shared index. It uses sqlite and has some date pre-populated. Start up script/server and do a search for “John”.
37 comments
That’s fantastic, thanks for the writeup. Last time I look at Ferret it felt a bit contorted – this looks great though!
Any ideas about memory usage? I seem to remember it using a lot last time I used it…
Are you using Ferret on any production apps? If so, how has it been? I considered Ferret a few months ago, but went for Sphinx when a lot of people were saying Ferret isn’t as stable.
I have to report stability issues with Ferret. Sphinx is serving well and exposes some advanced features Ferret is lacking.
Hi, I’m following your pattern but having problems with rendering. I have my models mapped as resources but when I call
I get an exception, “couldn’t find template array/_arrays”
Instead of a SearchResults object, I appear to be getting a plain array returned. Any ideas?
you’ve gotten farther then I damon and brandon.
i get
when i reload the server. I did get the same message as Damon however, when i tried this yesterday without restarting the server. i never should have restarted my server.I’m thinking that something in trunk is borked, right now.
also, branden, in the paragraph where you say to skip the patch , your tutorial/post goes right to more code.
Was your intent for ‘us’ to skip that bit of code too? it kind of just appears and then you say… now for the fun part.
1000 thank you’s if you get this working for me boss.
config.after_initialize....<pre> needs to be in your controller(s) / view(s).It’s actually the
that i am having the trouble with.Where did you put that? did you put that in your environment.rb? and if so, where?
i get a message saying that load_config cannot be found. load_config is part of the plugin… but for some reason it can’t be found when initializing.
I have the latest aaf and didn’t need to do anything to my environment.rb.
Once I made the changes, I did restart ferret_server.
Sorry, half asleep here…
To answer your question, I am not using anywhere. Once I restarted my ferret server, everything worked fine. If you’re not using a ferret server, you might want to look at reloading your index files (but I’m just guessing).the blog post that became a forum…. i bet if we help each other, this posting will go down as the posting that changed searching with ferret forever. First, thanks damon. for some reason it’s working now. i got rid of all reference to aaf in my app and started with a new trunk plugin. so far so good. at least my server starts now.
So what do you use in your view then?
i keep getting blank views.
here is my searches/index.html.erb<h1>Search Results</h1> <%= params.inspect %> <% @results.each do |result| %> <%= render :partial => "searches/#{dom_class(result)}" %> <% end %>and let’s say i have a _user.html.erb partial for the user’s name i want to search. here is the corresponding partial What am i doing wrong? What does yours look like? THIS is the part that is screwing with my head. p.s., i just wanted something to show up when i searched, hence the lame partial. i’ll pimp it up when i get it working.<%= link_to_if(result.instance_of?(Activity), result.name, activity_path(result)){ link_to result.name, organization_path(result) } %>. . . .I am sure there is a better way to do this but I don’t know how. However, once I know what object I’m dealing with, I access it normally.i don’t know where my last comment post went… but, i was saying, if we can talk about this on railsforum, it may be a better spot for us.
You’re the only dude i know with a similar issue, hope you don’t mind.
Hi Brandon,
Is it possible to display your views, and search fields please?
When i pass params[:q], all i get is the name in which i typed.
Somehow, params[:q] doesn’t work it’s way through the ‘shared’ index? it must be how i’m passing the search term.
Perhaps you could do another post just devoted to the little bits, like the views and search forms, that were skipped in this post. Much appreciated, and keep up the good work.
Very nice code, I am not quite clear about passing these parameters, but it make some sense for me.
Thanks, Eddie Funeral
I’ve put together an example rails app that uses the shared index. It uses sqlite and has some date pre-populated. Start up script/server and do a search for “John”.
I’ve also removed parts of the post that may have led to confusion. Hope that helps.
Appreciate the demo. will look tomorrow. it worked for me.
I’m wondering, if in the mean time, would myself using MySQL have any impact on why it hasn’t worked for me thus far?
the demo works like a champ, and i’m hoping it will help me fix my issues :-)
thanks and i’ll report back to my favourite forum…err… blog. cheers.
day two friend.
your demo works fantastic.
i tried all of the same code, but added my models instead. One of which is a Post with title and body….
anyways, the only difference in the two apps is you use Postgres and i use mysql.
i doubt that could be it, could it? the ferret forum is being of no help… would hoped by now that someone would have picked it up, i.e., answer my plea!!!!!!!!!!!!!!!!
day two friend.
your demo works fantastic.
i tried all of the same code, but added my models instead. One of which is a Post with title and body….
anyways, the only difference in the two apps is you use Postgres and i use mysql.
Also, i’ve got my search input form inside of my layouts file. could that have something to do with it???? I’ve got no other answers. i’m tapped out, starting to cry. this is the last piece i need before i conquer the world with my new app!!!!!
i doubt that could be it, could it? the ferret forum is being of no help… would hoped by now that someone would have picked it up, i.e., answer my plea!!!!!!!!!!!!!!!!
Brandon, thanks for the post and letting us work through it in your comments. P jam, could you post your code over at Rails Forum ?
Apologize for the delay…was away over the weekend.
Usefull post!
how do I search only Person, Company with the shared index? with: ActsAsFerret::find(“test”,[Person,Company]) it seems still returns all the result from Person , Company , Post
mikeg,
I’m not sure that it’s possible using the shared index, but you can simply define another index that has just those two models.
hello,
Thanks for the tutorial. It works fine, except when I try to render the partial.
I have two models, Reviews and Comments.
If my search term finds only Reviews, it successfully renders the results in the review_results partial.
If my search term finds only Comments, it correctly renders the results in the comment_results partial.
But if my search term finds both Reviews and Comments, it tries to render Reviews in the comment_results partial and Comments in the review_results partial, which fails because the two tables have different fields.
Have you encountered this? Are you able to get two models to display in their respective partial views correctly? Any ideas as to why it’s not working for me?
Thanks,
Terri
Terri,
That’s odd. Are you doing something different from the render code above?
You might want to try updating your version of Rails. I don’t remember exactly which version fixed it, but Rails used to only look at the first model in an array to figure out which partial to use. Update to the next version (2.0 if you’re on 1.2 or 2.1 if you’re on 2.0) and it should work.
Hi Brandon,
I’m running Rails 2.1.
I was able to get this to work by adding the :locals parameter to the render partial statement so it reads:
<%= render :partial => “shared/search_results_#{dom_class(result)}”, :locals => {:”#{dom_class(result)}” => result } %>
Thanks again for the tutorial.
Terri
Nice tutorial but I have a couple of questions…
How can I use highlighting with shared index?
Its possible to use boosts in the fields?
Thank’s in advance!
just so that we have at least ONE source with a working shared index, i’d like to give back. Pagination. To paginate a shared index in acts_as_ferret, add the following code to your controller, (rather then what is in the tutorial):
per_page = 2 @results = ActsAsFerret.find(params[:q], 'index_name', :page => params[:page], :per_page => per_page)Hope this helps someone. Oh, and make sure you use the latest will_paginate from github, in rails 2.1Hi,
I’ve setup everything as explained in the article but I continue to get this error: undefined method `find’ for ActsAsFerret:Module
I’ve tried to Google the problem but I got no answers found… Anybody solved it? Max
Regarding the problem above and Damon’s replies to the problem that seem unrelated to the issue:
undefined method `load_config’ for ActsAsFerret:Module (NoMethodError)
It’s caused by forgetting to uninstall the gem while running with the local copy of the gem. Delete the gem and everything will work
Hi, change <%= render :partial => “search/#{dom_class(result)}” %> to <%= render :partial => “search/#{dom_class(result)}”, :object => @result %>
Hi, change <%= render :partial => “search/#{dom_class(result)}” %> to <%= render :partial => “search/#{dom_class(result)}”, :object => result %>
I’m using Ferret as gem on windows. And I’m using acts_as_ferret gem. I put require ‘acts_as_ferret’ in the emvironment.rb. And put acts_as_ferret on model. But when I run Model.find_with_ferret(query) on console I have no result, just a blank array. Do you know how to solved it?
very nice post!!. Thank you for the all the useful info.
what if two models have the same field name ? for example i have two models “student” and “teacher” both have a field name called “name”. How to i set “default_fields” for both these models ? Will this syntax takes care of both ? ferret => { :default_fields => [:name]}
This post is really useful!! Is it possible to highlight the query in the results?
Which kind of ferret search does find_with_ferret use? And how can I change the type of search?
Thank your!
Speak your mind: