Model.search
    Tse-Ching Ho
     2010-04-25
Ruby Conf Taiwan 2010
ActiveRecord
ActiveRecord v.s. Arel
module ActiveRecord
 class Base
  class < self
    def unscoped
     @unscoped ||= Relation.new(self, arel_table)
     finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
    end
    def arel_table
     @arel_table ||= Arel::Table.new(table_name, :engine => arel_engine)
    end
  end
 end
end
module ActiveRecord::NamedScope::ClassMethods
 def scoped(options = {}, &block)
  if options.present?
    relation = scoped.apply_finder_options(options)
    block_given? ? relation.extending(Module.new(&block)) : relation
  else
    current_scoped_methods ? unscoped.merge(current_scoped_methods) :
unscoped.clone
  end
 end
end
Scope with Arel
class Product < ActiveRecord::Base
 scope :name_like, lambda { |param|
   where(self.arel_table[:name].matches("%#{param}%"))
 }
 scope :attr_like, lambda { |attr, param|
   where(self.arel_table[attr].matches("%#{param}%"))
 }
 scope :attr_gt, lambda { |attr, param|
   where(self.arel_table[attr].gt(param))
 }
end
Product.name_like('Ruby')
Product.attr_like(:name, 'Ruby')
   SELECT "products".* FROM "products"
   WHERE ("products"."name" LIKE '%Ruby%')
Product.attr_gt(:price, 100)
   SELECT "products".* FROM "products"
   WHERE ("products"."price" > 100)
AREL
Match => LIKE

NotMatch => NOT LIKE
NotMatch ?
products = Product.scoped
puts products.where(products.table[:name].matches('%Ruby%')).to_sql
  SELECT "products".* FROM "products"
  WHERE ("products"."name" LIKE '%Ruby%')

products = Product.scoped
puts products.where(%Q{"products"."name" NOT LIKE '%Ruby%'}).to_sql
puts products.where(%Q{"products"."name" NOT LIKE ?}, '%Ruby%').to_sql
  SELECT "products".* FROM "products"
  WHERE ("products"."name" NOT LIKE '%Ruby%')

products = Product.scoped
products.where(products.table[:name].notmatches('%Ruby%'))

products = Product.search
products.name_not_like = 'Ruby'

products = Product.search('name_not_like' => 'Ruby')
NotMatch !
require 'arel'
# lib/arel/algebra/predicates.rb
module Arel::Predicates
 class NotMatch < Binary; end
end
# lib/arel/algebra/attributes/attribute.rb
module Arel
 class Attribute
  def notmatches(regexp); Predicates::NotMatch.new(self, regexp) end
 end
end
# lib/arel/engines/sql/predicates.rb
module Arel::Predicates
 class NotMatch < Binary
  def predicate_sql; 'NOT LIKE' end
 end
end
# lib/arel/engines/memory/predicates.rb
module Arel::Predicates
 class NotMatch < Binary
  def operator; :"!~" end
 end
end
Rails 2.3 => Search Logic

Rails 3 => Search Logic
Write Your Own
Search Method !!!
Meta Search
http://github.com/ernie/meta_search
MetaSearch::Searches::Base



module MetaSearch::Searches::Base
 def search(opts = {})
  search_options = opts.delete(:search_options) || {}
  builder = MetaSearch::Builder.new(self, search_options)
  builder.build(opts)
 end
end
ActiveRecord::Base.send :include, MetaSearch::Searches::ActiveRecord


               Product.search('name_not_like' => 'Ruby')
MetaSearch::Builder
module MetaSearch
 class Builder
  attr_reader :base, :relation, :join_dependency
  delegate :joins, :includes, :all, :count, :to_sql, :paginate, :find_each,
            :first, :last, :each, :to => :relation
  def initialize(base, opts = {})
    @base = base
    @associations = {}
    @join_dependency =
ActiveRecord::Associations::ClassMethods::JoinDependency.new(@base, [],
nil)
    @relation = @base.scoped
  end
  def build(opts)
    @relation = @base.scoped
    opts.each_pair {|k, v| self.send("#{k}=", v)}
    self
  end                                      name_not_like=
 end
end
MetaSearch::Builder
module MetaSearch               name_not_like=
 class Builder
  def method_missing(method_id, *args, &block)
   if match = matches_attribute_method(method_id)
     condition, attribute, association = match.captures.reverse
     build_method(association, attribute, condition)
     self.send(preferred_method_name(method_id), *args)
   elsif match = matches_where_method(method_id)
     condition = match.captures.first
     build_where_method(condition, Where.new(condition))
     self.send(method_id, *args)
   else
      super      @relation.where(products.table[:name]
   end           .notmatches('%Ruby%')
  end
 end
end
MetaSearch::Where
MetaSearch::Where.add(['not_like', 'not_contain', 'notmatches', {
    :types => [:string, :text, :binary],
    :condition => :notmatches,           Arel:: Attribute#notmatches
    :formatter => '"%#{param}%"'
}])
@@wheres['not_like'] = {
  :name => 'not_like',
  :aliases => ['not_contain', 'notmatches'],
  :types => [:string, :text, :binary],
  :condition => :notmatches,
  :formatter => Proc.new {|param| eval '"%#{param}%"'},
  :validator => Proc.new {|param| !param.blank?},
  :splat_param => false
}
#=> Where.new(@@wheres['not_like'])

Where.new('not_like') == Where.get('not_like') # if new with string
More Possibilities ?

Article.where(:created_at > 100.days.ago, :title =~ 'Hi%').to_sql
 SELECT "articles".* FROM "articles"
 WHERE ("articles"."created_at" > '2010-01-05 20:11:44.997446')
 AND ("articles"."title" LIKE 'Hi%')




http://gist.github.com/265308
http://github.com/ernie/meta_where
About Me


  Tse-Ching Ho
http://github.com/tsechingho



   http://grassbrook.com
END

model.search: customize your own search logic

  • 1.
    Model.search Tse-Ching Ho 2010-04-25 Ruby Conf Taiwan 2010
  • 2.
  • 3.
    ActiveRecord v.s. Arel moduleActiveRecord class Base class < self def unscoped @unscoped ||= Relation.new(self, arel_table) finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped end def arel_table @arel_table ||= Arel::Table.new(table_name, :engine => arel_engine) end end end end module ActiveRecord::NamedScope::ClassMethods def scoped(options = {}, &block) if options.present? relation = scoped.apply_finder_options(options) block_given? ? relation.extending(Module.new(&block)) : relation else current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone end end end
  • 4.
    Scope with Arel classProduct < ActiveRecord::Base scope :name_like, lambda { |param| where(self.arel_table[:name].matches("%#{param}%")) } scope :attr_like, lambda { |attr, param| where(self.arel_table[attr].matches("%#{param}%")) } scope :attr_gt, lambda { |attr, param| where(self.arel_table[attr].gt(param)) } end Product.name_like('Ruby') Product.attr_like(:name, 'Ruby') SELECT "products".* FROM "products" WHERE ("products"."name" LIKE '%Ruby%') Product.attr_gt(:price, 100) SELECT "products".* FROM "products" WHERE ("products"."price" > 100)
  • 5.
  • 6.
  • 7.
    NotMatch ? products =Product.scoped puts products.where(products.table[:name].matches('%Ruby%')).to_sql SELECT "products".* FROM "products" WHERE ("products"."name" LIKE '%Ruby%') products = Product.scoped puts products.where(%Q{"products"."name" NOT LIKE '%Ruby%'}).to_sql puts products.where(%Q{"products"."name" NOT LIKE ?}, '%Ruby%').to_sql SELECT "products".* FROM "products" WHERE ("products"."name" NOT LIKE '%Ruby%') products = Product.scoped products.where(products.table[:name].notmatches('%Ruby%')) products = Product.search products.name_not_like = 'Ruby' products = Product.search('name_not_like' => 'Ruby')
  • 8.
    NotMatch ! require 'arel' #lib/arel/algebra/predicates.rb module Arel::Predicates class NotMatch < Binary; end end # lib/arel/algebra/attributes/attribute.rb module Arel class Attribute def notmatches(regexp); Predicates::NotMatch.new(self, regexp) end end end # lib/arel/engines/sql/predicates.rb module Arel::Predicates class NotMatch < Binary def predicate_sql; 'NOT LIKE' end end end # lib/arel/engines/memory/predicates.rb module Arel::Predicates class NotMatch < Binary def operator; :"!~" end end end
  • 9.
    Rails 2.3 =>Search Logic Rails 3 => Search Logic
  • 10.
  • 11.
  • 12.
    MetaSearch::Searches::Base module MetaSearch::Searches::Base defsearch(opts = {}) search_options = opts.delete(:search_options) || {} builder = MetaSearch::Builder.new(self, search_options) builder.build(opts) end end ActiveRecord::Base.send :include, MetaSearch::Searches::ActiveRecord Product.search('name_not_like' => 'Ruby')
  • 13.
    MetaSearch::Builder module MetaSearch classBuilder attr_reader :base, :relation, :join_dependency delegate :joins, :includes, :all, :count, :to_sql, :paginate, :find_each, :first, :last, :each, :to => :relation def initialize(base, opts = {}) @base = base @associations = {} @join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@base, [], nil) @relation = @base.scoped end def build(opts) @relation = @base.scoped opts.each_pair {|k, v| self.send("#{k}=", v)} self end name_not_like= end end
  • 14.
    MetaSearch::Builder module MetaSearch name_not_like= class Builder def method_missing(method_id, *args, &block) if match = matches_attribute_method(method_id) condition, attribute, association = match.captures.reverse build_method(association, attribute, condition) self.send(preferred_method_name(method_id), *args) elsif match = matches_where_method(method_id) condition = match.captures.first build_where_method(condition, Where.new(condition)) self.send(method_id, *args) else super @relation.where(products.table[:name] end .notmatches('%Ruby%') end end end
  • 15.
    MetaSearch::Where MetaSearch::Where.add(['not_like', 'not_contain', 'notmatches',{ :types => [:string, :text, :binary], :condition => :notmatches, Arel:: Attribute#notmatches :formatter => '"%#{param}%"' }]) @@wheres['not_like'] = { :name => 'not_like', :aliases => ['not_contain', 'notmatches'], :types => [:string, :text, :binary], :condition => :notmatches, :formatter => Proc.new {|param| eval '"%#{param}%"'}, :validator => Proc.new {|param| !param.blank?}, :splat_param => false } #=> Where.new(@@wheres['not_like']) Where.new('not_like') == Where.get('not_like') # if new with string
  • 16.
    More Possibilities ? Article.where(:created_at> 100.days.ago, :title =~ 'Hi%').to_sql SELECT "articles".* FROM "articles" WHERE ("articles"."created_at" > '2010-01-05 20:11:44.997446') AND ("articles"."title" LIKE 'Hi%') http://gist.github.com/265308 http://github.com/ernie/meta_where
  • 17.
    About Me Tse-Ching Ho http://github.com/tsechingho http://grassbrook.com
  • 18.