Module: Redis::TextSearch::ClassMethods

Defined in:
lib/redis/text_search.rb

Overview

These class methods are added to the class when you include Redis::TextSearch.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#redisObject

Returns the value of attribute redis.



39
40
41
# File 'lib/redis/text_search.rb', line 39

def redis
  @redis
end

#text_index_exclude_listObject

Words to exclude from text indexing. By default, includes common English prepositions like “a”, “an”, “the”, “and”, “or”, etc. This is an accessor to an array, so you can use += or << to add to it. See the constant DEFAULT_EXCLUDE_LIST for the default list.



46
47
48
# File 'lib/redis/text_search.rb', line 46

def text_index_exclude_list
  @text_index_exclude_list
end

#text_indexesObject (readonly)

Returns the value of attribute text_indexes.



40
41
42
# File 'lib/redis/text_search.rb', line 40

def text_indexes
  @text_indexes
end

Instance Method Details

#delete_text_indexes(id, *fields) ⇒ Object

Delete all text indexes for the given id.



186
187
188
189
190
191
192
193
194
195
196
# File 'lib/redis/text_search.rb', line 186

def delete_text_indexes(id, *fields)
  fields = @text_indexes.keys if fields.empty?
  fields.each do |field|
    redis.pipelined do |pipe|
      text_indexes_for(id, field).each do |key|
        pipe.srem(key, id)
      end
      pipe.del field_key("#{field}_indexes", id)
    end
  end
end

#field_key(name, id) ⇒ Object

:nodoc:



58
59
60
# File 'lib/redis/text_search.rb', line 58

def field_key(name, id) #:nodoc:
  "#{prefix}:#{id}:#{name}"
end

#merge_text_search_conditions!(ids, options) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/redis/text_search.rb', line 79

def merge_text_search_conditions!(ids, options)
  pk = "#{table_name}.#{primary_key}"
  case options[:conditions]
  when Array
    if options[:conditions][1].is_a?(Hash)
      options[:conditions][0] = "(#{options[:conditions][0]}) AND #{pk} IN (:text_search_ids)"
      options[:conditions][1][:text_search_ids] = ids
    else
      options[:conditions][0] = "(#{options[:conditions][0]}) AND #{pk} IN (?)"
      options[:conditions] << ids
    end
  when Hash
    if options[:conditions].has_key?(primary_key.to_sym)
      raise BadConditions, "Cannot specify primary key (#{pk}) in :conditions to #{self.name}.text_search"
    end
    options[:conditions][primary_key.to_sym] = ids
  when String
    options[:conditions] = ["(#{options[:conditions]}) AND #{pk} IN (?)", ids]
  else
    options.merge!(:conditions => {primary_key => ids})
  end
end

#prefixObject

:nodoc:



50
51
52
53
54
55
56
# File 'lib/redis/text_search.rb', line 50

def prefix #:nodoc:
  @prefix ||= self.name.to_s.
    sub(%r{(.*::)}, '').
    gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
    gsub(/([a-z\d])([A-Z])/,'\1_\2').
    downcase
end

#prefix=(prefix) ⇒ Object

Set the Redis prefix to use. Defaults to model_name



49
# File 'lib/redis/text_search.rb', line 49

def prefix=(prefix) @prefix = prefix end

#text_filter(field) ⇒ Object

Filter and return self. Chainable.

Raises:

  • (UnimplementedError)


181
182
183
# File 'lib/redis/text_search.rb', line 181

def text_filter(field)
  raise UnimplementedError
end

#text_index(*args) ⇒ Object

Define fields to be indexed for text search. To update the index, you must call update_text_indexes after record save or at the appropriate point.

Raises:

  • (ArgumentError)


104
105
106
107
108
109
110
111
112
# File 'lib/redis/text_search.rb', line 104

def text_index(*args)
  options = args.last.is_a?(Hash) ? args.pop : {}
  options[:minlength] ||= 2
  options[:split] ||= /\s+/
  raise ArgumentError, "Must specify fields to index to #{self.name}.text_index" unless args.length > 0
  args.each do |name|
    @text_indexes[name.to_sym] = options.merge(:key => field_key(name, 'text_index'))
  end
end

#text_indexes_for(id, field) ⇒ Object

:nodoc:



198
199
200
# File 'lib/redis/text_search.rb', line 198

def text_indexes_for(id, field) #:nodoc:
  (redis.get(field_key("#{field}_indexes", id)) || '').split(';')
end

#text_search(*args) ⇒ Object

Perform text search and return results from database. Options:

'string', 'string2'
:fields
:page
:per_page


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/redis/text_search.rb', line 120

def text_search(*args)
  options = args.length > 1 && args.last.is_a?(Hash) ? args.pop : {}
  fields = Array(options.delete(:fields) || @text_indexes.keys)
  finder = options.delete(:finder)
  unless finder
    unless defined?(:text_search_find)
      raise NoFinderMethod, "Could not detect how to find records; you must def text_search_find()"
    end
    finder = :text_search_find
  end

  #
  # Assemble set names for our intersection.
  # Accept two ways of doing search: either {:field => ['value','value'], :field => 'value'},
  # or 'value','value', :fields => [:field, :field].  The first is an AND, the latter an OR.
  #
  ids = []
  if args.empty?
    raise ArgumentError, "Must specify search string(s) to #{self.name}.text_search"
  elsif args.first.is_a?(Hash)
    sets = []
    args.first.each do |f,v|
      sets += text_search_sets_for(f,v)
    end
    # Execute single intersection (AND)
    ids = redis.set_intersect(*sets)
  else
    fields.each do |f|
      sets = text_search_sets_for(f,args)
      # Execute intersection per loop (OR)
      ids += redis.set_intersect(*sets)
    end
  end

  # Assemble our options for our finder conditions (destructive for speed)
  recalculate_count = options.has_key?(:conditions)

  # Calculate pagination if applicable. Presence of :page indicates we want pagination.
  # Adapted from will_paginate/finder.rb
  if options.has_key?(:page)
    page = options.delete(:page) || 1
    per_page = options.delete(:per_page) || self.per_page

    Redis::TextSearch::Collection.create(page, per_page, nil) do |pager|
      # Convert page/per_page to limit/offset
      options.merge!(:offset => pager.offset, :limit => pager.per_page)
      if ids.empty?
        pager.replace([])
        pager.total_entries = 0
      else
        pager.replace(send(finder, ids, options){ |*a| yield(*a) if block_given? })
        pager.total_entries = recalculate_count ? wp_count(options, [], finder.to_s) : ids.length  # hacked into will_paginate for compat
      end
    end
  else
    # Execute finder directly
    ids.empty? ? [] : send(finder, ids, options)
  end
end

#text_search_find(ids, options) ⇒ Object

This is called when the class is imported, and uses reflection to guess how to retrieve records. You can override it by explicitly defining a text_search_find class method that takes an array of IDs as an argument.



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/redis/text_search.rb', line 65

def text_search_find(ids, options)
  if defined?(ActiveModel)
    # guess that we're on Rails 3
    raise "text_search_find not implemented for Rails 3 (yet) - patches welcome"
  elsif defined?(ActiveRecord::Base) and ancestors.include?(ActiveRecord::Base)
    merge_text_search_conditions!(ids, options)
    all(options)
  elsif defined?(Sequel::Model) and ancestors.include?(Sequel::Model)
    self[primary_key.to_sym => ids].filter(options)
  elsif defined?(DataMapper::Resource) and included_modules.include?(DataMapper::Resource)          
    get(options.merge(primary_key.to_sym => ids))
  end
end

#text_search_sets_for(field, values) ⇒ Object



202
203
204
205
206
207
208
# File 'lib/redis/text_search.rb', line 202

def text_search_sets_for(field, values)
  key = @text_indexes[field][:key]
  Array(values).collect do |val|
    str = val.downcase.gsub(/[^\w\s]+/,'').gsub(/\s+/, '.')  # can't have " " in Redis cmd string
    "#{key}:#{str}"
  end
end