miércoles, 19 de diciembre de 2007

multimodel search with acts_as_ferret

Hi everyone!,

First of all, I want to introduce myself to you people. I'm Marcelo Giorgi an Uruguayan developer who has a strong background in web technologies (mainly using J2EE and PHP), but lately I found myself very excited studiyng Ruby and RoR, why? I just can't explaing it, it was love at first sight ^_^.

I want to share with you how you can use acts_as_ferret plugin, for Ruby On Rails, with multiple models (http://projects.jkraemer.net/acts_as_ferret/), such an excellent search engine. I had to research a bit to keep things up and running, hopefully it could help someone...

Install acts_as_ferret

The installation can be accomplished by executing the following line on the rails project:

script/plugin install svn://projects.jkraemer.net/acts_as_ferret/tags/stable/acts_as_ferret

And that's it, now we can start messing around with it!

Use single model for a query

Before showing some complex query, I'll be showing the basic usage by example. Suppose you want to make a search through an Article model. To make this model searcheable we got to invoke the acts_as_ferret helper within the class definition, as shown:

article.rb
class Article < style="font-style: italic;"> ..
acts_as_ferret
..
end

Now, we simple need to define a new controller that make use of the new search feature, we named this controller as SearchEngineController:

search_engine_controller.rb
class SearchEngineController < collection =" Article.find_with_ferret params[:q], :page => params[:page]
end
end

Finally, we just need to modify the view to show the results:

app/views/search_engine_controller/index.rhtml
...

As you might noticed, we used will_paginate helper, that enables us paginate the results provided by acts_as_ferret.

Use many models for a query

Until this point, all seems very straighforwad (didn't it?). The documentation of find_with_ferret indicates that we simply code :store_class_name => :true to enable search over multiple models. Then, we only have to use find_with_ferret with the appropiate paramters to indacate in which models we want to search, this can be done with this:

search_engine_controller.rb
class SearchEngineController < style="font-style: italic;"> def index
@collection = Article.find_with_ferret params[:q], :page => params[:page], :multi => [NewsItem]
end
end

This code mandates that the search must be perfomed through the Article and NewsItem's models. Everything seems right, but...

But, when we run it, we get the following error:


RuntimeError in Search resultController#index
':store_class_name => true' required for multi_search to work

The problem is that multisearh requires us, to create the index over the models we will use in the search. This can be accomplished with a rake task. As the below code suggest.

lib/task/ferret.rake
namespace :ferret do
desc "Rebuild all Indexes"
task :rebuild_all_indexes => [:environment] do
%w(Article NewsItem).each { |s| s.constantize.rebuild_index }
end
end
Then, we just got to run the task 'rebuild_all_indexes' before we start the server and everthing should work just fine.

5 comentarios:

nerbie69 dijo...

hello,

I'm wondering if you could expand on your index view... as this way of searching is different than that in which i've seen before. But i like it.

you're index.rhtml file isn't showing up, if you could put that, it would be great.

mgiorgi dijo...

Sorry for the delay, Hope it helps :P

#index.rhtml
..
< ul >
< % @collection.each do |item| >
< %= render(:partial => item.class.to_s.tableize.singularize + '_result', :object => item) >
< % end >
< %= will_paginate @collection >
< /ul >
..

Here as you can see, I have a partial for each kind of class item that the search retrieves. Below I show you a partial sample, for articles:

#_article_result.rhtml
< li >
Article -
< %= link_to(article_result.title, article_path(article_result)) %>
< %= article_result.highlight(params[:q], :field => :body, :num_excerpts => 1, :pre_tag => "< strong >", :post_tag => "< /strong >") % >
< /li >

In this one, I highlighted the keyword of the search for each result instance.

P.D: Sorry about the syntax, but blogger is not smart enough to scape some tokens :(

Luck!
Marcelo.

Anónimo dijo...

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

Anónimo dijo...

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 as follows. (Since this blogger.com comment system won't accept html, replace [ and ] with the less-than-percent and greater-than-percent formats used in Rails views).

[ @results.each do |result| ]
[= render :partial => "shared/search_results_#{result.class.to_s.downcase}", :locals => {:"#{result.class.to_s.downcase}" => result } ]
[ end ]

Thanks,

Terri

iPad Application Development dijo...

Thank you a lot for providing individuals with an extremely nice chance to read critical reviews from this web site.