`
hideto
  • 浏览: 2678414 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

每天一剂Rails良药之acts_as_ferret

    博客分类:
  • Ruby
阅读更多
Ferret是Ruby的文本搜索引擎,它基于Apache Lucene

安装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
分享到:
评论
6 楼 heweiya 2008-07-25  
首先感谢你的文章,在配置使用当中有一些难题,尤其是在做高亮的时候,请教问题如下:Highlighting 当中的修改方法:def find_storage_by_contents(query, options = {})
当中的index = self.ferret_index是个什么东东,好像没有这个ferret_index函数,是不是就是analyzer = RMMSeg::Ferret::Analyzer.new { |tokenizer|
    Ferret::Analysis::LowerCaseFilter.new(tokenizer)
  }
  $index = Ferret::Index::Index.new(:analyzer => analyzer)
如果有源代码下载就好了,通过这个文章我学习了很多东西,但是就是在做一些配置时遇到了难题,希望博主回复这些问题.
谢谢!
5 楼 zhangzldipan 2008-07-24  
4 楼 zhangzldipan 2008-06-22  
查了那么多次内容,看了那么多你的文章,没一次从你身上得到好处!真该好好检讨下!郁闷……
3 楼 hideto 2007-09-26  
ferret是一个gem
2 楼 blackanger 2007-09-24  
acts_as_ferret和ferret什么关系?
我没有装ferret,在用acts_as_ferret的时候报错no such file to load -- ferret,装了ferret以后才正常。acts_as_ferret是基于ferret的?
1 楼 sina2009 2007-09-14  
不知道怎么回事,模糊查询Person.find_by_contents("an~")查询不到任何条目

Person.find_by_contents("andy")可以查询出条目

不知道有何解决方案

相关推荐

    acts_as_authenticated

    "acts_as_authenticated" 是一个经典的Ruby on Rails插件,它为Rails应用提供了用户认证功能。在Rails框架中,用户认证通常涉及验证用户身份、管理会话以及处理登录和登出等操作。acts_as_authenticated插件简化了这...

    rails_admin_acts_as_list:rails_admin插件以对记录进行排序

    介绍插件,用于对记录进行排序(使用 gem)安装要启用rails_admin_acts_as_list,请将以下内容添加到您的Gemfile : gem 'rails_admin_acts_as_list'gem 'rails_admin' 重要提示: rails_admin_acts_as_list之前必须...

    acts_as_paranoid

    "acts_as_paranoid" 是一个在Ruby on Rails框架中常用的gem,用于处理数据库记录的软删除(soft delete)。在数据库操作中,通常我们会遇到删除数据的需求,但直接删除可能会导致数据丢失,尤其是在生产环境中。...

    用acts_as_rateable实现简单的打分功能

    在Ruby on Rails框架中,`acts_as_rateable`是一个非常实用的插件,它允许用户对模型进行评分,从而实现简单的打分功能。这个插件是Rails社区中的一个开源项目,旨在简化应用中的评分系统集成。在本篇讨论中,我们将...

    acts_as_nested_set的增强版BetterNestedSet

    acts_as_nested_set是Rails社区中广泛使用的Nested Set库,而BetterNestedSet则是它的增强版,提供了更多功能和优化。本文将深入探讨BetterNestedSet的原理、用法以及相较于acts_as_nested_set的优势。 1. Nested ...

    acts_as_category:想想acts_as_tree +权限

    #ActsAsCategory acts_as_category (Version 2.0 beta)acts_as_category,是acts_as插件在acts_as_tree风格的Ruby on Rails的ActiveRecord的模式,但有一些额外的功能,以及多种便捷视图助手。例子(有关实例方法和...

    actions_as_commentable:ActiveRecord acts_as_commentable插件

    它创建一个Comment模型,并处理该模型与您声明为可注释模型的任何模型之间的关系。 安装: 将以下行添加到您的Gemfile中 Rails gem 'acts_as_commentable' Rails gem 'acts_as_commentable' , '3.0.1' Rails ...

    acts_as_paranoid:ActiveRecord插件可让您隐藏和还原记录,而无需实际删除它们

    使徒行传 一个Rails插件来添加软删除。...用法安装gem: gem 'acts_as_paranoid' , '~&gt; 0.7.0' bundle install创建迁移bin/rails generate migration AddDeletedAtToParanoiac deleted_at:datetime:index启用ActsAs

    acts_as_restful_list:就像acts_as_list一样,但很安静

    acts_as_restful_list 就像acts_as_list 一样,但不必使用非标准方法调用(如insert_at)来弄乱您的代码,acts_as_restful_list 使管理列表变得简单。 您可以像更新其他任何内容一样更新 position 属性,其余的都...

    Api-acts_as_api.zip

    Api-acts_as_api.zip,使在rails中创建api响应变得简单和有趣,一个api可以被认为是多个软件设备之间通信的指导手册。例如,api可用于web应用程序之间的数据库通信。通过提取实现并将数据放弃到对象中,api简化了编程...

    acts_as_liked:向任何 Active Record 模型添加类似功能

    $ rails generate acts_as_liked 并且不要忘记迁移您的数据库 $ rake db:migrate 用法 可爱的模特 将acts_as_likeable添加到任何模型,它的实例可以被其他模型喜欢。 class Post &lt; ActiveRecord :: Base ...

    acts_as_commentable_with_threading:类似于acts_as_commentable; 然而,利用 awesome_nested_set 提供线程评论

    作为可评论行为(现在有评论线程(TM)!!!——在(TM)上开玩笑) 允许将线程注释添加到多个不同的模型。 与acts_as_commentable 兼容(但需要更改数据库架构) ... rails generate acts_as_commentable_with_

    偏执狂:Rails 3、4和5的acts_as_paranoid

    偏执狂将继续接受错误修复并支持Rails的新版本,但不接受新功能。 偏执狂 偏执狂是Rails 3/4/5的的重新实现,使用的代码少得多。 当您的应用程序使用偏执狂时,在ActiveRecord对象上调用destroy并不会真正破坏...

    acts_as_xapian:Xapian全文搜索插件,适用于Ruby on Rails

    1. **无缝集成**:acts_as_xapian能够无缝地与Rails模型集成,只需在模型中添加一行`acts_as_xapian`代码,即可开启对该模型的全文搜索支持。 2. **自定义索引**:开发者可以自由决定哪些字段参与索引,甚至可以...

    acts_as_aliased:扩展 ActiveRecord

    rails generate acts_as_aliased:install rake db:migrate 这将创建一个新表aliases 。 用法 假设您有一个需要别名的模型Company ,因为公司名称有不同的版本。 使用acts_as_aliased在模型中启用别名: model ...

    acts_as_shopping_cart:简单的购物车实施

    acts_as_shopping_cart 一个简单的购物车实现。 您可以找到示例应用程序。 安装 滑轨3 从0.2.0版开始,不再支持Rails 3。 如果您仍需要在Rails 3应用程序中实现此gem,请使用0-1-x分支 将其包含在您的Gemfile中 ...

    acts_as_nps_rateable:Rails 4.x的一个宝石Ruby,实现了Net Promoter Score

    actions_as_nps_rateable act_as_nps_rateable为基于ActiveRecord的模型提供净促销值(NPS)评级和分析。 促销员净额是衡量客户满意度的指标; 它是会推荐您的产品/服务的客户比例与不会推荐您的产品/服务的客户...

    acts_as_owner.rb:Rails 的简单所有权解决方案

    Acts as owner 是 Ruby on Rails 的一个插件,它为所有者对象提供了自我查询可拥有对象的所有权的能力。 可拥有对象可以是属于所有者对象的任何对象和属于可拥有对象的任何对象。 属于拥有的可拥有的父代的任何可...

    Secode_level_cache.zip

    现在我们将这个插件从Rails2.x的版本升级到了3.x版本,并且抽取成了一个通用插件,开始应用于新的Rails3.2的项目之上。有志于AR对象缓存优化的ruby程序员不容错过。 使用方法: class User acts_as_cached(:...

    Rails101_by_rails4.0

    《Rails101_by_rails4.0》是一本专注于Rails 4.0.0版本和Ruby 2.0.0版本的自学教程书籍,它定位于中文读者,旨在成为学习Rails框架的参考教材。Rails(Ruby on Rails)是一个采用Ruby语言编写的开源Web应用框架,它...

Global site tag (gtag.js) - Google Analytics