Ferret,是用ruby开发的基于Apache Lucene的全文检索引擎库, 安装Ferret:
gem install ferret
在ferret的代码中,只有少量的ruby代码,大部分是c代码。这里有Ferret API,并在其中提供了一份教程Ferret Tutorial。
Ferret是ruby库,在rails中如果想使用,就要用到Jens Kramer 的Acts As Ferret了,它提供了简单的接口,我们可以快速的创建复杂的搜索索引。
在你的rails中,以插件的形式安装acts_as_ferret
ruby script/plugin install svn://projects.jkraemer.net/acts_as_ferret/tags/stable/acts_as_ferret
下面我们从一个简单的用法开始。
首先需要在你的model中添加需要被索引 的项目
class Member < ActiveRecord::Base
acts_as_ferret :fields => [:first_name, :last_name]
end
在这里我们是将特定的名字进行索引,搜索的时候将返回这些索引的符合结果。
Acts as Ferret为你的ActiveRecord model增加了搜索方法, 和其他的教程不同,我们使用find_id_by_contents:
当我们调用
total_results, members = Member.find_id_by_contents(”Gregg”)
那么:
members = [
{:model => “Member”, :id => “4″, :score => “1.0″},
{:model => “Member”, :id => “21″, :score => “0.93211″},
{:model => “Member”, :id => “27″, :score => “0.32212″}
]
我们得到了前10条记录(当然我只显示了3条),包括了每条记录的id和搜索得分(search scores)。
但是,当返回记录有40条的时候,我们仍将得到10条记录。
find_id_ by_contents可以传递一些参数进去:
results = []
total_results = Member.find_id_by_contents(”Gregg”) {|result|
results.push result
}
在这,你可能并不想只得到结果的id,那么可以在这里做一个转换,得到返回的model集合。
results = []
total_results = Member.find_id_by_contents(”Gregg”) {|result|
results.push Member.find(result[:id])
}
使用find_by_contents
@results = Member.find_by_contents(”Gregg”)
find_by_contents进行了下面的操作:
注意哦
members = Member.find_by_contents(”Gregg”)
# It gives us total hits!
puts “Total hits = #{members.total_hits}”
for member in members
puts “#{member.first_name} #{member.last_name}”# And the search Score!
puts “Search Score = #{member.ferret_score}”
end
注意里面的total_hits和ferret_score,在数据库中他们并不存在的哦。
注:下面代码中使用了Roman Mackovcak’s blog的例子。
在model中添加下面这个方法
def self.full_text_search(q, options = {})
return nil if q.nil? or q==”"
default_options = {:limit => 10, :page => 1}
options = default_options.merge options# get the offset based on what page we’re on
options[:offset] = options[:limit] * (options.delete(:page).to_i-1)# now do the query with our options
results = Member.find_by_contents(q, options)
return [results.total_hits, results]
end
在你的application.rb中添加
def pages_for(size, options = {})
default_options = {:per_page => 10}
options = default_options.merge options
pages = Paginator.new self, size, options[:per_page], (params[:page]||1)
return pages
end
在controller中添加
def search
@query = params[:query]
@total, @members = Member.full_text_search(@query, :page => (params[:page]||1))
@pages = pages_for(@total)
end
在页面中
<%= link_to ‘Previous page’, { :page => @pages.current.previous, :query => @query} if @pages.current.previous %>
<%= pagination_links(@pages, :params => { :query=> @query }) %>
<%= link_to ‘Next page’, { :page => @pages.current.next, :query => @query} if @pages.current.next %>
上面的代码可以完成大部分工作了,但是acts_as_ferret还有其他的优秀特性的。
更多复杂查询,可以参考 Apache Lucene Parser Syntax。
现在对我们例子做一个修改,我们有许多书,每本书有一些作者,如果你不仅要索引书的标题,还要索引书的作者,该怎么办呢?
我们需要操作的是两个表,可是我们不能去对两个不同的索引进行查找。这时需要修改我们的model
/model/book.rb
class Book < ActiveRecord::Base
acts_as_ferret :fields => [:title, :author_name]def author_name
return “#{self.author.first_name} #{self.author.last_name}”
end
end
这样在搜索书的标题时,书的作者也能被搜索到。
你可以对任何model方法的返回值进行索引,甚至可以重新格式化你的字段(fields)。
比如你在使用 acts_as_taggable ,对你的model进行tag标注,并且希望在搜索的时候你的tag一并被搜索到。那么:
class Book < ActiveRecord::Base
acts_as_taggable
acts_as_ferret :fields => [:title, :tags_with_spaces]def tags_with_spaces
return self.tag_names.join(” “)
end
end
原文:If you were using the acts_as_taggable plugin you might not even need the extra function, and use “:tag_list” in the ferret field list, as shown onJohnny’s Thoughts. I’m not nearly as cool though, I’m using the acts_as_taggable gem.
译文:如果你在以插件形式使用acts_as_taggable,那么就会出现Johnny’s Thoughts中提到的问题,(具体情况大家去看那个博客吧,这个地方留意一下)。这时你需要在ferret索引字段列表使用”:tag_list”。译者:因为作者也在用插件形式使用ferret。插件冲突的事情由此也需要注意了。
到目前为止,我们得到的记录都是排好序列的搜索结果。那么如果我们想得到按照我们想根据的字段进行排列的结果,比如书的标题,该如何做呢?
原文:The first thing you need to do is make sure the field you are trying to sort by is untokenized. Unfortunately, by making a field untokenized I’m not indexing it to be searchable anymore. This makes for a little funky coding.
作者:”if something is untokenized it will not be searchable ”
译者:首先你需要确定,需要排序的字段没有被索引过。所以我们要对上面的代码做一点修改。
acts_as_ferret :fields => {
:title => {},
:tags_with_spaces => {},
:title_for_sort => {:index => :untokenized}
}def title_for_sort
return self.title
end
记得,如果你想重新建立索引,只需要删除对应的文件夹,并重启服务。
译者:重建索引也可以实用Model.rebuild_index这个方法。
s = Ferret::Search::SortField.new(:title_for_sort, :reverse => false)
@total, @members = Book.full_text_search(@query,
{:page => (params[:page]||1), :sort => s})
这样我就得到了按照书的title的排序。
如果你想按照日期排序,那就需要把日期转换成integer类型,具体的请参考this Slash Dot Dash
在进入下一节(相当重要的一节)之前,我们需要研究下如何储存已被索引的数据(data)。
如果你现在看一下你的索引文件(indexes),你会发现那里并没有你的数据。默认情况下,acts_as_ferret在这种可复写的情况下并不储存你的数据(in a recoverable form),仅是索引它。
“那么,如果我的数据很小,我想在我的索引中储存它,该怎么办呢?”
这是个好问题,如果你的数据很小,而且你只是关注一个字段的信息,你可以加速你的索引。
acts_as_ferret :fields => {
:title => {:store => :yes},
:author_name => {:store => :yes}
}
当我们进行查询时,我们需要对这个特殊的字段进行说明(”lazy load”)
@books = Book.find_by_contents(”Jason”, :lazy => [:title, :author_name])
这样我们在渲染页面时,并没有调用数据库。
< % @books.each do |book| %>
%lt;li> “< %= book.title %>” by
< %= book.author_name %>%lt;/li%gt;
< % end %>
下面是用ferret实现搜索词的高亮显示。
不过这有个前提,就是需要对你的搜索词进行储存(must have your search fields stored),上面已经介绍了。
所以需要对上面的代码做一点点修改了:
< % @books.each do |book| %>
<li>
“< %= book.highlight(”Jason”, :field => :title, :num_excerpts => 1, :pre_tag => ““, :post_tag => ““) %>” by
< %= book.highlight(”Jason”, :field => :author_name, :num_excerpts => 1, :pre_tag => ““, :post_tag => ““) %>
</li>
< % end %>
你的搜索结果会是:
1. “Story of Gregg” by Jason Seifer
2. “Jason’s Book” by Gregg Pollack
3. “Gregg certainly is the Man” by Jason Seifer
高亮显示功能还有其他的方法,比如,如果你搜索的字段内容很长,例如博客文章,那么将会返回一个片断,搜索词被高亮显示。更多参考 Highlight in the API
最后介绍一下Boost属性。Boost属性可以提升索引的优先顺序。
acts_as_ferret :fields => {
:title => {:boost => 2},
:author => {:boost => 0}
}
这段代码表明,title搜索结果要优先于author结果的显示。但是这并不是说,所有的title记录在author记录前显示。如果一条author记录完全匹配搜索词,那么它会优先显示。
Perhaps this feature should be called “Nudge” instead of “Boost”. I thought I could use a large boost to get all the title results to appear above the author results. I was mistaken, one can only “Nudge” the scores, but never separate them, as I was hoping.
译者:大家看一下那个连接的文章,这段英文的意思是,使用boost是提升部分优先级,而别指望它能把title和author分开。
因为很多人是在产品环境下使用,所以一致的想法认为,你需要运行一个DRB Server