Using shared indexes with acts_as_ferret

ferret | rails | search April 28 2008

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

posted by brandon | updated May 11th 10:32 PM
comments feed

24 comments

  1. 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…

    Ed Spencer Ed Spencer
    April 29, 2008 at 01:18 PM
  2. 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.

    Derek Derek
    April 29, 2008 at 07:44 PM
  3. I have to report stability issues with Ferret. Sphinx is serving well and exposes some advanced features Ferret is lacking.

    ynw ynw
    April 29, 2008 at 08:38 PM
  4. Hi, I’m following your pattern but having problems with rendering. I have my models mapped as resources but when I call

    <%= render :partial => @results %>

    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?

    Damon Damon
    May 07, 2008 at 02:28 PM
  5. you’ve gotten farther then I damon and brandon.

    i get

    undefined method `load_config' for ActsAsFerret:Module (NoMethodError)
    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.

    P jam P jam
    May 07, 2008 at 04:12 PM
  6. 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.

    P jam P jam
    May 07, 2008 at 04:25 PM
  7. P jam, all the code after
    config.after_initialize....<pre>
    needs to be in your controller(s) / view(s).
                
    Damon Damon
    May 07, 2008 at 06:25 PM
  8. (try again) P jam, all the code after
    config.after_initialize....
    needs to be in your controller(s) / view(s).
    Damon Damon
    May 07, 2008 at 06:27 PM
  9. It’s actually the

    config.after_initialize....
    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.

    p jam p jam
    May 07, 2008 at 08:36 PM
  10. 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.

    Damon Damon
    May 07, 2008 at 11:08 PM
  11. Sorry, half asleep here…

    To answer your question, I am not using
    config.after_initialize....
    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).
    Damon Damon
    May 07, 2008 at 11:11 PM
  12. 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
    <%= link_to result.login, root_url %>
    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.
    p jam p jam
    May 08, 2008 at 10:46 AM
  13. The view is where things get ugly for me.
    <%= render :partial => @results %>
    Doesn’t look for other partials based on the model name (e.g., activities/_activity partial if the model is Activity), so I threw something together that’s very ugly in a _result partial:
    <%= 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.
    Damon Damon
    May 08, 2008 at 01:42 PM
  14. 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.

    http://www.railsforum.com/viewtopic.php?id=18060
    p jam p jam
    May 08, 2008 at 04:13 PM
  15. 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.

    p jam p jam
    May 09, 2008 at 10:39 AM
  16. Very nice code, I am not quite clear about passing these parameters, but it make some sense for me.

    Thanks, Eddie Funeral

    Funeral Funeral
    May 11, 2008 at 05:45 PM
  17. 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.

    Brandon Brandon
    May 11, 2008 at 10:36 PM
  18. 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.

    p jam p jam
    May 11, 2008 at 11:13 PM
  19. 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!!!!!!!!!!!!!!!!

    p jam p jam
    May 12, 2008 at 09:39 PM
  20. 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!!!!!!!!!!!!!!!!

    p jam p jam
    May 12, 2008 at 09:40 PM
  21. 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.

    Damon Damon
    May 13, 2008 at 10:08 AM
  22. Usefull post!

    Sebastian Sebastian
    May 14, 2008 at 02:39 PM
  23. 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 mikeg
    July 06, 2008 at 05:07 PM
  24. 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.

    Brandon Brandon
    July 11, 2008 at 08:10 AM

Speak your mind:

(Required)

(Required)


(You may use textile in your comments.)

About

I'm Brandon Keepers, a web application developer that likes beautiful code, valid markup and adherence to standards. As a part of Collective Idea in Holland, Michigan, I practice Agile software development primarily using Ruby on Rails.

-86.103171 42.785037

Contact:

more »

Syndicate