发现还有人在看这个博客,这个 SearchLogic 只支持 Rails 2。Rails 3 有一个类似的插件叫 meta search,后来 meta search 又被重新改写,并改名叫 ransack,功能跟 searchlogic 非常相似,如果你在用 Rails 3,推荐使用 ransack。
参考:http://github.com/binarylogic/searchlogic
比较早就在用这个插件了,本来今天想响应蜗牛同学的号召,把这篇http://asciicasts.com/episodes/176-searchlogic给译一下,结果才译到第2句,发现译不下去。。还是乖乖写笔记吧。
Searchlogic是一个功能十分强大使用又很简单的Rails搜索插件。(
BinaryLogic出品的两个插件真的很不错:
Authlogic和
Searchlogic)
安装:
script/plugin install git://github.com/binarylogic/searchlogic.git
扩展
装上之后,所有的model都被添加上了一系列的类方法(其实是通过method_missing实现的,见源码:
http://github.com/binarylogic/searchlogic/blob/master/lib/searchlogic/named_scopes/conditions.rb)
这一系列的方法很容易看懂,直接copy官方的代码吧:
#假设我们有如下的model
User(id: integer, created_at: datetime, username: string, age: integer)
# Searchlogic会免费赠送给你这么一堆named scopes
User.username_equals("bjohnson")
User.username_equals(["bjohnson", "thunt"])
User.username_equals("a".."b")
User.username_does_not_equal("bjohnson")
User.username_begins_with("bjohnson")
User.username_not_begin_with("bjohnson")
User.username_like("bjohnson")
User.username_not_like("bjohnson")
User.username_ends_with("bjohnson")
User.username_not_end_with("bjohnson")
User.age_greater_than(20)
User.age_greater_than_or_equal_to(20)
User.age_less_than(20)
User.age_less_than_or_equal_to(20)
User.username_null
User.username_not_null
User.username_blank
这些方法的返回值都是一个named scope对象,因此你可以把它们串起来使用:
scope = User.username_like("bjohnson").age_greater_than(20).id_less_than(55)
scope.all
scope.first
scope.count
# 等等...等啥?在rails console里试试看看效果哇
完整的条件(conditions)列表见Searchlogic::NamedScopes::Conditions中的常量。
别名
作者说了,输入长长的“greater_than_or_equal_to”一点也不好玩,所以作者又为searchlogic添加了一系列别名:
User.username_is(10) # equals
User.username_eq(10) # equals
User.id_lt(10) # less than
User.id_lte(10) # less than or equal to
User.id_gt(10) # greater than
User.id_gte(10) # greater than or equal to
# 等等...
这些还不是searchlogic强大的地方。
searchlogic还可以
搜索关联的对象(关联的数据库表),很简单:
# 假设我们有以下的关联
User.has_many :orders
Order.has_many :line_items
LineItem
# 设置关联对象的字段的搜索条件
User.orders_total_greater_than(20)
User.orders_line_items_price_greater_than(20)
# 按照关联对象的某个字段排序
User.ascend_by_order_total
User.descend_by_orders_line_items_price
wow 你一定已经惊呼了,不过还早。
自定义的named scope
因为这些方法的返回值都是named_scope,所以还可以这样用:
LineItem.named_scope :expensive, :conditions => "line_items.price > 500"
User.orders_line_items_expensive
多态关联
更强大的来了:
searchlogic为多态关联也提供了很好的支持:
#假设有这样的关联:
Audit.belongs_to :auditable, :polymorphic => true
User.has_many :audits, :as => :auditable
#可以这样搜索
Audit.auditable_user_type_username_equals("ben")
方法的格式有点特别,包含了这样的格式的前缀:
[多态关联的名字]_[关联的类型]_type
不过还是比较好懂的,只要给关联起个好名字,读起来还是很DSL的
强吧?还早还早
all 和any
User.username_like_any("bjohnson", "thunt") # 会发起一个查询,查找users表中username包含字符串bjohnson或者thunt的记录
User.username_like_all("bjohnson", "thunt") # 会发起一个查询,查找users表中username同时包含字符串bjohnson和thunt的记录
User.username_like_any(["bjohnson", "thunt"]) # 也可以接受一个数组形式的参数
or的使用
User.username_or_first_name_like("ben")
=> "username LIKE '%ben%' OR first_name like'%ben%'"
User.id_or_age_lt_or_username_or_first_name_begins_with(10)
=> "id < 10 OR age < 10 OR username LIKE 'ben%' OR first_name like'ben%'"
注意,你并不需要每次都清楚的加上条件(例如gt、lt、begins_with等)。你只需要在结尾的部分指定。如果你在一个字段名后面加上另一个字段名,那么对这两个字段都将使用后面的条件。所以:
User.username_like_or_first_name_like("ben")
可以写成这样:
User.username_or_first_name_like("ben")
Make searching and ordering data in your application trivial
差不多已经可以使用Searchlogic了,但是更精彩的还在这后面。如果仅仅提供上面这些model的接口,你要如何跟controllers和views结合起来使用?当然使用Searchlogic提供的这些接口比使用Rails本身的接口方便多了,但是还不够方便。
Searchlogic另外提供了一个search方法,该方法接受一个hash,返回的是一个Searchlogic::Search对象。
可以这样用:
search = User.search(:username_like => "bjohnson", :age_less_than => 20)
search.all
上面的代码相当于:
User.username_like("bjohnson").age_less_than(20).all
这个Searchlogic::Search对象有点类似named scope,它也不会立即发起SQL查询。也可以进行链式的调用以添加搜索条件,也可以读取搜索条件。
search.username_like # => "bjohnson"
search.age_gt = 2 #=> 2
search.id_gt(10).email_begins_with("bjohnson") # => <#Searchlogic::Search...>
search.all #=> An array of users
search.count #=> integer
# .. 等等
看起来似乎search方法更麻烦了些,但是这个hash……嗯,Controller中的params是个hash……想到了吧。
ok,现在从Controller开始,controller就这么干净:
class UsersController < ApplicationController
def index
@search = User.search(params[:search])
@users = @search.all
end
end
页面是这样的(haml的代码,不明白可以当它是html.erb。什么是
haml):
- form_for @search do |f|
= f.text_field :username_like
= f.select :age_greater_than, (0..100)
= f.text_field :orders_total_greater_than
= f.submit
searchlogic改写了form_for方法,使它能够认识Searchlogic::Search对象,并在参数为Searchlogic::Search对象时,把form的method转为get。来看看源码:
# Automatically makes the form method :get if a Searchlogic::Search and sets
# the params scope to :search
def form_for(*args, &block)
if search_obj = args.find { |arg| arg.is_a?(Searchlogic::Search) }
options = args.extract_options!
options[:html] ||= {}
options[:html][:method] ||= :get
options[:url] ||= url_for
args.unshift(:search) if args.first == search_obj
args << options
end
super
end
另外SearchLogic还带了一个排序的order helper:
= order @search, :by => :age
= order @search, :by => :created_at, :as => "Created date"
这会在面面上生成2个链接,效果如下图:
看看表头第一列那个Order Date,一看就知道是干什么用的了
利用现有的named scope
如果你已经定义了这样的一个named scope:
User.named_scope :four_year_olds, :conditions => {:age => 4}
可以这样用:
User.search(:four_year_olds => true, :username_like => "bjohnson")
然后你的表单是这样的:
- form_for @search do |f|
= f.text_field :username_like
= f.check_box :four_year_olds
= f.submit
注意这里传了个true给named scope,如果原来定义的named scope不接受任何参数,可以在这里传个true或者false给它,如果传的是个false,那么这个named scope将被忽略。如果named scope接受参数,那么这个值会被当作参数传给named scope。
分页:
和
will_paginate结合使用,很简单:
User.username_like("bjohnson").age_less_than(20).paginate(:page => params[:page])
User.search(:username_like => "bjohnson", :age_less_than => 20).paginate(:page => params[:page])
__END__
cookie:
今天还发现一个好玩的小东西,叫faker,可以生成随机的姓名、email等等。蛮好玩的。
gem install faker
irb
require 'rubygems'
require 'faker'
Faker::Name.name
Faker::Name.name
Faker::Name.name
Faker::Internet.email
Faker::Internet.email
Faker::Internet.email