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”.








24 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…
April 29, 2008 at 01:18 PM
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.
April 29, 2008 at 07:44 PM
I have to report stability issues with Ferret. Sphinx is serving well and exposes some advanced features Ferret is lacking.
April 29, 2008 at 08:38 PM
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?
May 07, 2008 at 02:28 PM
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.
May 07, 2008 at 04:12 PM
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.
May 07, 2008 at 04:25 PM
config.after_initialize....<pre> needs to be in your controller(s) / view(s).May 07, 2008 at 06:25 PM
May 07, 2008 at 06:27 PM
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.
May 07, 2008 at 08:36 PM
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.
May 07, 2008 at 11:08 PM
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).May 07, 2008 at 11:11 PM
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.May 08, 2008 at 10:46 AM
<%= 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.May 08, 2008 at 01:42 PM
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.
May 08, 2008 at 04:13 PM
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.
May 09, 2008 at 10:39 AM
Very nice code, I am not quite clear about passing these parameters, but it make some sense for me.
Thanks, Eddie Funeral
May 11, 2008 at 05:45 PM
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.
May 11, 2008 at 10:36 PM
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.
May 11, 2008 at 11:13 PM
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!!!!!!!!!!!!!!!!
May 12, 2008 at 09:39 PM
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!!!!!!!!!!!!!!!!
May 12, 2008 at 09:40 PM
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.
May 13, 2008 at 10:08 AM
Usefull post!
May 14, 2008 at 02:39 PM
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
July 06, 2008 at 05:07 PM
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.
July 11, 2008 at 08:10 AM
Speak your mind: