使用Ultrasphinx实现多Model搜索

Posted by ashchan on February 26th, 2008 filed in Plugin, Rails应用

Sphinx是由俄罗斯(让人想起了Nginx:)人Andrew开发的全文搜索引擎。Ultrasphinx则是一个使用该引擎的Rails插件。本文介绍如何使用Ultrasphinx使用多Model搜索。

系统环境:

  • Leopard 10.5.2
  • MySql 5.0.45
  • Rails 2.0.2

注:Linux与之类似。Win平台需选择安装Sphinx的windows版本。

安装

  1. 安装mysql-devel
  2. 下载Sphinx 0.9.8源码,解压后安装:
     ./configure
    make
    sudo make install
  3. 安装chronic gem:
    sudo gem install chronic
  4. 安装Ultrasphinx:
    script/plugin install svn://rubyforge.org/var/svn/fauna/ultrasphinx/trunk

配置

将vender/plugins/ultrasphinx/examples/default.base拷贝至/config/ultrasphinx/default.base。为了支持中文,修改该文件,加上以下参数:

ngram_len = 1

ngram_chars = U+4E00..U+9FBB, U+3400..U+4DB5, U+20000..U+2A6D6, U+FA0E, U+FA0F, U+FA11, U+FA13, U+FA14, U+FA1F, U+FA21, U+FA23, U+FA24, U+FA27, U+FA28, U+FA29, U+3105..U+312C, U+31A0..U+31B7, U+3041, U+3043, U+3045, U+3047, U+3049, U+304B, U+304D, U+304F, U+3051, U+3053, U+3055, U+3057, U+3059, U+305B, U+305D, U+305F, U+3061, U+3063, U+3066, U+3068, U+306A..U+306F, U+3072, U+3075, U+3078, U+307B, U+307E..U+3083, U+3085, U+3087, U+3089..U+308E, U+3090..U+3093, U+30A1, U+30A3, U+30A5, U+30A7, U+30A9, U+30AD, U+30AF, U+30B3, U+30B5, U+30BB, U+30BD, U+30BF, U+30C1, U+30C3, U+30C4, U+30C6, U+30CA, U+30CB, U+30CD, U+30CE, U+30DE, U+30DF, U+30E1, U+30E2, U+30E3, U+30E5, U+30E7, U+30EE, U+30F0..U+30F3, U+30F5, U+30F6, U+31F0, U+31F1, U+31F2, U+31F3, U+31F4, U+31F5, U+31F6, U+31F7, U+31F8, U+31F9, U+31FA, U+31FB, U+31FC, U+31FD, U+31FE, U+31FF, U+AC00..U+D7A3, U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9, U+A000..U+A48C, U+A492..U+A4C6

由于我们要对多个Model进行搜索,并且希望能得到每个Model的结果统计,需要对 Ultrasphinx进行额外的配置。在config/initializers下添加ultrasphinx.rb文件,内容如下:

Ultrasphinx::Search.client_options[:with_subtotals] = true

实现

假设我们有两个Model需要支持搜索,一个为Post,一个为User,Post需要搜索title属性,User需要搜索name属性。在Post类里加上:

is_indexed :fields => ['title']

在User类里加上:

is_indexed :fields => ['name']

Ultrasphinx提供了几个rake任务来管理索引和索引服务。首先,用以下任务来生成配置文件(在default.base基础上根据加上了is_indexed方法的Model自动生成配置);生成的配置文件也位于config/ultrasphinx目录,名称为[RAILS_ENV].conf,比方说development环境下生成的是development.conf:

rake ultrasphinx:configure

接下来,生成索引:

rake ultrasphinx:index

完成后(非常快),启动索引服务:

rake ultrasphinx:daemon:start

一切准备妥当,可以来实现搜索功能了。以SearchController为例,在其index action中实现如下逻辑:

1
2
3
4
5
6
7
8
9
10
11
class SearchController < ApplicationController
def index
  @search = Ultrasphinx::Search.new(
    :query => params[:q],
    :class_names => SearchHelper::class_name(params[:category]),
    :page => params[:page] || 1
    )
  @search.run
end
 
end

index.html.erb视图如下:

1
2
<%= output_results(@search) %>
<%= will_paginate(@search) %>

你可能已经注意到了,是的,Ultrasphinx可以跟will_paginate无缝集成,cool!

Ultrasphinx::Search.new方法中,传入的:query参数是查询关键字,:class_names是一个数组或字符串,指定要查询的Model类。我们在 SearchHelper中实现class_name方法来判断用户要查询的Model类,实现output_results方法来输出查询结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
module SearchHelper
def output_results(search)
  stat = "搜索结果:"
  search.results.each do |result|
    stat << (render :partial =&gt; "/search/#{result.class.to_s.downcase}", :object =&gt; result)
  end
 
  stat << '共 ' << search.total_entries.to_s << ' 条结果。'
 
  search.subtotals.each do |category, count|
    stat << SearchHelper::ui_name(category) << ':' << count.to_s << " 条结果\t"
  end
  stat
end
 
def self.class_name(category_value)
  SEARCH_CATEGORIES.detect do |c|
    c[2] == category_value
  end[1]
end
 
def self.ui_name(class_name)
  SEARCH_CATEGORIES.detect do |c|
    c[1] == class_name
  end[0]
end
 
SEARCH_CATEGORIES = [
# uiname, classname, uivalue
['所有', nil, 'all'],
['文章', 'Post', 'post'],
['用户', 'User', 'user']    ].freeze
end

另外,我们在页面上提供一个搜索表单给用户,让他可以选择搜索文章、用户,还是所有内容:

1
2
3
4
5
6
7
8
<form action="/search" method="get" id="search_form">
<label for="search_category">类别:</label>
<select id="search_category" name="category"></select><label for="q">关键字:</label>
<input name="q" value="<%= params[:q] %>" id="q" type="text" />
 
<input value="搜索" type="submit" >
 
</form>

最后,把_post.html.erb和_user.html.erb这两个视图文件扔到search的视图目录下。里面加上显示一条相关Model的搜索结果的展示即可。

结论:

通过Ultrasphinx插件,非常容易实现多Model全文搜索。其要点是:

  • 使用Ultrasphinx::Search.new生成search对象时使用class_names参数指定要搜索的Model的名称(或使用nil值指定搜索所有Model);
  • 设定Ultrasphinx::Search.client_options[:with_subtotals] = true以支持各Model搜索结果的统计,并使用搜索结果的subtotals hash获取这些统计;
  • 加上ngram_chars和ngram_len参数,以支持中文搜索(同时需保证MySql数据表使用utf8编码)。

Related posts:

  1. ultrasphinx step by step ultrasphinx是基于sphinx的rails全文搜索插件。我们将在一个已有的博客系统上增加全文搜索来学习这个插件, 1. 首先确保系统已经安装以下软件: * MySQL 5.0 * Sphinx 0.9.8-dev...
  2. Jquery 表格工具 — 2dc_jqgrid————– 它整合了 InPlaceEdit,分页,搜索,添加,删除等功能,甚至还可以处理附属关系的表格 功能不可谓不强大,但代码却是来的简单的很! ————————– - Ajax enabled - Sorting -...
  3. rails中联级菜单的一种实现方式 通过Ajax实现联级菜单。 典型应用场景:两个下拉框(select),第一是省份的选择,第二个是城市的选择。在省份选择定之后,第二个select显示选中省份对应的城市。也就是说城市要根据所选的省份过滤。 实现方式:在切换省份的同时,向server端请求对应省份下的city,生成select HTML代码替换原city select, 具体如下。view中: 省份: remote_function(:with => "'province_id='...

Related posts brought to you by Yet Another Related Posts Plugin.

Leave a Comment