原创作者: hideto
阅读:2655次
评论:0条
更新时间:2011-05-26
Ferret是Ruby的文本搜索引擎,它基于Apache Lucene
安装Ferret非常简单:
Ferret是一堆C代码的Ruby代码封装,Ferret是针对Ruby的而不是RoR的
而Acts As Ferret则是针对RoR的
我们有两种方式安装Acts As Ferret:
1,以gem方式安装
然后在environment.rb里添加
2,以plugin方式安装
(虽然推荐以插件方式安装acts_as_ferret,但貌似这样安装不起作用,svn地址改为acts_as_ferret也不行,再议)
先看一个简单的例子,我们先给需要做index的model加上如下代码:
然后假如我们做如下search;
这将发生如下事情:
1,在Rails程序目录下创建/index/development/member子目录,所有的index文件将在这里创建
2,Members的first/last name将加入到该index,以后每次add/update/delete一个member都会增量更新index
如果你需要重新生成index,只需删除相应的文件目录并重启服务器,下次search时就会重新生成index
3,ActsAsFerret将会在我们的index上调用Ferret的search_each方法
4,我们将得到10条如下形式的结果:
我们得到每个member的id和search score
即使有超过10条的结果,我们默认将只能得到10条返回的结果
Options
1,offset,默认为0
2,limit,默认为10,:all将返回所有结果
3,sort
如果我们需要查询结果的数量
或者这样做
如果我们需要查询结果为model
我们有更好的办法,find_by_contents
这将做如下事情:
1,首先使用find_id_by_contents得到ids
2,使用返回的ids来查询Model
3,返回ActsAsFerret::SearchResults对象Array(可以认为是ActiveRecord对象,但是多一些额外的特性)
我们可以做如下的事情
注意total_hits和ferret_score就是额外的特性
对搜索结果分页
1,对你的model加上如下方法
修改application.rb
然后修改controller
在view中的代码
查询字符串
1,搜索"Gregg Pollack"将返回在ANY域中以ANY顺序排列的包含"Gregg"和"Pollack"的结果
2,搜索"Gregg OR Pollack"将返回包含"Gregg"或"Pollack"的结果
3,搜索"Gregg~"模糊查询,将返回包含"Gregg"的结果
4,搜索"first_name:Gregg"将返回first name为"Gregg"的结果
5,搜索"+first_name:Gregg -last_name:Jones"将返回first name为"Gregg"并且last name不是"Jones"的结果
更复杂的查询条件参考Apache Lucene Parser Syntax
搜索多个表
也就是说我们可以搜索model方法返回的任何东西,甚至是tags
排序
假如我们需要对title搜索并对title排序,但是排序的field要求不能被index和search,我们可以这样做
然后我们可以在controller里按title排序搜索了
存储数据
默认情况下acts_as_ferret只对数据做index而不存储数据,如果数据很小,我们可以这样做来在index里存储数据来加快search数据
我们给model添加如下search方法
这样我们根本就不用接触数据库就可以查询数据,view代码
Highlighting
我们来看看怎样用ferret做出Google搜索结果bold关键字的效果
前提是上面所说存储搜索的fields
我们修改上面的search方法
使用Boost
如果我们希望对title的搜索结果的score要比author的搜索结果的score稍高,我们可以使用boost参数
但是boost参数只能提高score,而不能分开title和author的搜索结果
Production环境下使用
由于性能问题,Production环境下最好以DRb Server的形式运行
注:本文参考Acts_As_Ferret Tutorial
安装Ferret非常简单:
gem install ferret
Ferret是一堆C代码的Ruby代码封装,Ferret是针对Ruby的而不是RoR的
而Acts As Ferret则是针对RoR的
我们有两种方式安装Acts As Ferret:
1,以gem方式安装
gem install acts_as_ferret
然后在environment.rb里添加
require 'acts_as_ferret'
2,以plugin方式安装
ruby script/plugin install svn://projects.jkraemer.net/acts_as_ferret/tags/stable/acts_as_ferret
(虽然推荐以插件方式安装acts_as_ferret,但貌似这样安装不起作用,svn地址改为acts_as_ferret也不行,再议)
先看一个简单的例子,我们先给需要做index的model加上如下代码:
class Member < ActiveRecord::Base acts_as_ferret :field => [:first_name, :last_name] end
然后假如我们做如下search;
members = Member.find_id_by_contents("Gregg")
这将发生如下事情:
1,在Rails程序目录下创建/index/development/member子目录,所有的index文件将在这里创建
2,Members的first/last name将加入到该index,以后每次add/update/delete一个member都会增量更新index
如果你需要重新生成index,只需删除相应的文件目录并重启服务器,下次search时就会重新生成index
3,ActsAsFerret将会在我们的index上调用Ferret的search_each方法
4,我们将得到10条如下形式的结果:
members = [ {:model => "Member", :id => "4", :score => "1.0"}, {:model => "Member", :id => "4", :score => "0.93211"}, {:model => "Member", :id => "4", :score => "0.32212"}, ... ]
我们得到每个member的id和search score
即使有超过10条的结果,我们默认将只能得到10条返回的结果
Options
1,offset,默认为0
2,limit,默认为10,:all将返回所有结果
3,sort
如果我们需要查询结果的数量
total_results = Member.total_hits("Gregg")
或者这样做
results = [] total_results = Member.find_id_by_contents("Gregg") { |result| results.push result }
如果我们需要查询结果为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")
这将做如下事情:
1,首先使用find_id_by_contents得到ids
2,使用返回的ids来查询Model
3,返回ActsAsFerret::SearchResults对象Array(可以认为是ActiveRecord对象,但是多一些额外的特性)
我们可以做如下的事情
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就是额外的特性
对搜索结果分页
1,对你的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
在view中的代码
<%= 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 %>
查询字符串
1,搜索"Gregg Pollack"将返回在ANY域中以ANY顺序排列的包含"Gregg"和"Pollack"的结果
2,搜索"Gregg OR Pollack"将返回包含"Gregg"或"Pollack"的结果
3,搜索"Gregg~"模糊查询,将返回包含"Gregg"的结果
4,搜索"first_name:Gregg"将返回first name为"Gregg"的结果
5,搜索"+first_name:Gregg -last_name:Jones"将返回first name为"Gregg"并且last name不是"Jones"的结果
更复杂的查询条件参考Apache Lucene Parser Syntax
搜索多个表
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方法返回的任何东西,甚至是tags
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
排序
假如我们需要对title搜索并对title排序,但是排序的field要求不能被index和search,我们可以这样做
acts_as_ferret :fields => { :title => {}, :tags_with_spaces => {}, :title_for_sort => {:index => :untokenized} } def title_for_sort return self.title end
然后我们可以在controller里按title排序搜索了
s = Ferret::Search::SortField.new(:title_for_sort, :reverse => false) @total, @members = Member.full_text_search(@query, {:page => (params[:page]||1), :sort => s}
存储数据
默认情况下acts_as_ferret只对数据做index而不存储数据,如果数据很小,我们可以这样做来在index里存储数据来加快search数据
acts_as_ferret :field => { :title => {:store => :yes}, :author_name => {:store => :yes} }
我们给model添加如下search方法
def self.find_storage_by_contents(query, options = {}) # Get the index that acts_as_ferret created for us index = self.ferret_index results = [] @ search_each is the core search function from Ferret, which Acts_as_ferret hides total_hits = index.search_each(query, options) do |doc, score| result = {} # Sotre each field in a hash which we can reference in our views result[:name] = index[doc][:name] result[:author_name] = idnex[doc][:author_name] # We can even put the score in the hash, nice! result[:score] = score results.push result end return block_given? ? total_hits : [ttal_hit, results] end
这样我们根本就不用接触数据库就可以查询数据,view代码
<% @results.each_with_index do |result, index| %> <%= index %>. <%= result[:name] %> by <%= result[:author_name] %> <br/> Score: <%= result[:score] %> <% end %>
Highlighting
我们来看看怎样用ferret做出Google搜索结果bold关键字的效果
前提是上面所说存储搜索的fields
我们修改上面的search方法
def find_storage_by_contents(query, options = {}) index = self.ferret_index # Get the index that acts_as_ferret created for us results = [] # search_each is the core search function from Ferret, which Acts_as_ferret hides total_hits = index.search_each(query, options) do |doc, score| result = {} # Store each field in a hash which we can reference in our views result[:name] = index.highlight(query, doc, :field => :name, :pre_tag => "<strong>", :post_tag => "</strong>", :num_excerpts => 1) result[:author_name] = index.highlight(query, doc, :field => :author_name, :pre_tag => "<strong>", :post_tag => "</strong>", :num_excerpts => 1) result[:score] = score # We can even put the score in the hash, nice! results.push result end return block_given? ? total_hits : [total_hits, results] end
使用Boost
如果我们希望对title的搜索结果的score要比author的搜索结果的score稍高,我们可以使用boost参数
acts_as_ferret :fields => { :title => {:boost => 2} :author => {:boost => 0} }
但是boost参数只能提高score,而不能分开title和author的搜索结果
Production环境下使用
由于性能问题,Production环境下最好以DRb Server的形式运行
注:本文参考Acts_As_Ferret Tutorial
评论 共 0 条 请登录后发表评论