Skip to content
jkraemer edited this page Sep 13, 2010 · 1 revision

Using Ferret with Typo

acts_as_ferret makes integrating Ferret into Typo really easy.

model

Typo uses Rails single table inheritance for storing articles, pages and comments. They’re all stored in a single table named contents. By letting acts_as_ferret work on the top level Content class, we can run searches across all content types.

Add the following to app/model/content.rb:

acts_as_ferret(
  :fields => { 'title'    => { :boost => 3 },
               'body'     => { :boost => 2 },
               'keywords' => { :boost => 2 },
               'extended' => { :boost => 2 },
               'author'   => { :boost => 1 },
               'email'    => { :boost => 1 },
               'url'      => { :boost => 1 },
               'name'     => { :boost => 1 } 
             },
  :store_class_name => true 
)
def self.ferret_find(query)
  find_by_contents(query).map { |content|
    if content.is_a? Comment
      content.article
    elsif content.published?
      content
    end
  }.flatten.uniq
rescue Exception => e
  []
end

The ferret_find method is what we’ll use in the controller.

controller

The Live Search is done by LiveController, where the the original search method is replaced with something like this:

app/controller/live_controller.rb

 def search
    @search = params[:q]
    @results = Content.ferret_find(@search) unless @search.to_s.blank?
    @headers[[Content-Type]] = "text/html; charset=utf-8"
  end

The non-Javascript search is done by ArticlesController :

app/controller/articles_controller.rb

def search
    @search = params[:q]
    @results = Content.ferret_find(params[:q]) unless @search.to_s.blank?
  end

view

As find_by_ferret returns Articles and Pages as well, it might not be suitable to display all the search results along with their contents on the results page. Instead, I’d suggest a search results page like this:
app/views/articles/search.rhtml

<div class="post">
  <h2>Searched for <em>"<%=h @search %>"</em></h2>
  <% if @results -%>
    <ul>
    <% for content in @results -%>
      <li><%= result_link content %> - <%= js_distance_of_time_in_words_to_now content.created_at %></li>
    <% end -%>
	  </ul>
  <% end -%>
</div>

Don’t forget to modify the template for the live search, too:

app/views/live/search.rhtml

<% unless @search.to_s.blank? -%>
  <h3>Searched for <em>"<%=h @search %>"</em></h3>
  <% if @results -%>
    <ul>
    <% for content in @results -%>
      <li><%= result_link content %> - <%= js_distance_of_time_in_words_to_now content.created_at %></li>
    <% end -%>
    </ul>
  <% end -%>
<% else %>
  &nbsp;
<% end -%>

helper

We still need to define that handy result_link function, which builds the correct
links to Pages and Articles:

app/helpers/application_helper.rb

def result_link(content)
  return article_link content.title, content if content.is_a? Article
  return page_link content.title, content if content.is_a? Page
end

try it out

Fire up your server and do a search. As there is no Ferret index yet, it will be built once the first search is run. The index location will be
RAILS_ROOT/index/RAILS_ENV/content and has to be writable by the user your server is running as.

Frequent Problems

No locale set

Current Versions of Ferret (that is, from 0.9 upwards) make some use of the System’s
locale settings to find out about the language that text to be indexed is in (I’m not sure if this is the real reason, please correct this if I’m wrong). On most servers the system locale is unset (and therefore has the default value ‘C’), which is something Ferret doesn’t like. I usually set

ENV['LANG'] = 'de_DE.UTF-8'
ENV['LC_TIME'] = 'C'

in config/environment.rb. You should replace the de_DE with your locale of choice, of course. The explicit setting of LC_TIME to ‘C’ is done because otherwise I’d have german month names in the Posted at strings, which the JavaScript conversion of dates to e.g. 10 days ago isn’t able to handle.

Clone this wiki locally