Module: PgSearchScope::ModelHelper

Defined in:
lib/pg_search_scope/model_helper.rb

Constant Summary collapse

DEFAULT_OPTIONS =
{
    :as => nil,
    :wildcard => true,
    :operator => :and,
    :normalization => 0,
    :select_rank => false,
    :language => 'simple'
}
OPERATORS =
{
    :and => '&',
    :or => '|'
}

Instance Method Summary collapse

Instance Method Details

#search_scope_for(*column_names) ⇒ Object

Creates fulltext search scope

Options

  • :as - Scope name

  • :normalization - Controls rank behaviour, see www.postgresql.org/docs/9.0/static/textsearch-controls.html#TEXTSEARCH-RANKING

  • :wildcard - Controls search words modification:

    true - add :* to ends of each search word
    false - do not modify search words
    :last - add :* to end of last word
    
  • :operator - Boolean operator (:and or :or) which combines search query

  • :select_rank - Include rank in select statement, as scope_name_rank

  • :language - Search language, e.g. ‘simple’ (without magic), ‘english’

Usage

search_scope_for :name
-->
search_by_name("Ivan")

search_scope_for :name, :address,
                 :wildcard => :last
-->
search_by_name_and_address("Ivan, Aurora st.", :select_rank => true)


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/pg_search_scope/model_helper.rb', line 46

def search_scope_for *column_names
  scope_options = DEFAULT_OPTIONS.merge column_names.extract_options!

  scope_name = scope_options[:as] || "search_by_#{column_names.join('_and_')}"

  scope scope_name, Proc.new { |search_string, options|
    options = scope_options.merge(options || {})
    search_string ||= ''

    terms = search_string.scan(/'*([\p{Lu}\p{Ll}\d\.']+)/u).map {|s,_| s.gsub /'/, "''"}

    if terms.present?
      prefix = arel_table.table_alias || arel_table.name
      document = column_names.map { |n| n = "#{prefix}.#{n}" unless n['.']; "coalesce(#{n}, '')" }.join(" || ' ' || ")

      case options[:wildcard]
        when true then
          terms.map! { |s| "#{s}:*" }
        when :last then
          terms[-1] = "#{terms[-1]}:*"
      end

      tsvector = "to_tsvector('#{options[:language]}', #{document})"
      tsquery = "to_tsquery('#{options[:language]}', '#{terms.join(" #{OPERATORS[options[:operator]]} ")}')"

      rank = "ts_rank(#{tsvector}, #{tsquery}, #{options[:normalization]})"

      search_scope = scoped

      if options[:select_rank]
        search_scope = search_scope.select("#{rank} #{scope_name}_rank")
      end

      search_scope.where("#{tsvector} @@ #{tsquery}").order("#{rank} DESC")
    else
      if options[:select_rank]
        scoped.select("0 #{scope_name}_rank")
      end
    end
  }
end