Module: ActiveRecord::Acts::Searchable::ClassMethods

Defined in:
lib/acts_as_searchable.rb

Constant Summary collapse

VALID_FULLTEXT_OPTIONS =
[:limit, :offset, :order, :attributes, :raw_matches, :find]

Instance Method Summary collapse

Instance Method Details

#acts_as_searchable(options = {}) ⇒ Object

Configuration options

  • searchable_fields - Fields to provide searching and indexing for (default: ‘body’)

  • attributes - Additional attributes to store in Hyper Estraier with the appropriate method supplying the value

  • if_changed - Extra list of attributes to add to the list of attributes that trigger an index update when changed

Examples:

acts_as_searchable :attributes => { :title => nil, :blog => :blog_title }, :searchable_fields => [ :title, :body ]

This would store the return value of the title method in the title attribute and the return value of the blog_title method in the blog attribute. The contents of the title and body columns would end up being indexed for searching.

Attribute naming

Attributes that match the reserved names of the Hyper Estraier system attributes are mapped automatically. This is something to keep in mind for custom ordering options or additional query constraints in fulltext_search For a list of these attributes see EstraierPure::SYSTEM_ATTRIBUTES or visit:

http://hyperestraier.sourceforge.net/uguide-en.html#attributes

From the example above:

Model.fulltext_search('query', :order => '@title STRA')               # Returns results ordered by title in ascending order
Model.fulltext_search('query', :attributes => 'blog STREQ poocs.net') # Returns results with a blog attribute of 'poocs.net'


113
114
115
116
117
118
119
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
# File 'lib/acts_as_searchable.rb', line 113

def acts_as_searchable(options = {})
  return if self.included_modules.include?(ActiveRecord::Acts::Searchable::ActMethods)

  send :include, ActiveRecord::Acts::Searchable::ActMethods
  
  cattr_accessor :searchable_fields, :attributes_to_store, :if_changed, :estraier_connection, :estraier_node,
    :estraier_host, :estraier_port, :estraier_user, :estraier_password

  self.estraier_node        = estraier_config['node'] || RAILS_ENV
  self.estraier_host        = estraier_config['host'] || 'localhost'
  self.estraier_port        = estraier_config['port'] || 1978
  self.estraier_user        = estraier_config['user'] || 'admin'
  self.estraier_password    = estraier_config['password'] || 'admin'
  self.searchable_fields    = options[:searchable_fields] || [ :body ]
  self.attributes_to_store  = options[:attributes] || {}
  self.if_changed           = options[:if_changed] || []
  
  send :attr_accessor, :changed_attributes

  class_eval do
    after_update  :update_index
    after_create  :add_to_index
    after_destroy :remove_from_index
    after_save    :clear_changed_attributes

    (if_changed + searchable_fields + attributes_to_store.collect { |attribute, method| method or attribute }).each do |attr_name|
      define_method("#{attr_name}=") do |value|
        write_changed_attribute attr_name, value
      end
    end

    connect_estraier
  end
end

#clear_index!Object

Clear all entries from index



209
210
211
# File 'lib/acts_as_searchable.rb', line 209

def clear_index!
  estraier_index.each { |d| estraier_connection.out_doc(d.attr('@id')) unless d.nil? }
end

#estraier_indexObject

:nodoc:



218
219
220
221
222
223
224
# File 'lib/acts_as_searchable.rb', line 218

def estraier_index #:nodoc:
  cond = EstraierPure::Condition::new
  cond.add_attr("type STREQ #{self.to_s}")
  result = estraier_connection.search(cond, 1)
  docs = get_docs_from(result)
  docs
end

#fulltext_search(query = "", options = {}) ⇒ Object

Perform a fulltext search against the Hyper Estraier index.

Options taken:

  • limit - Maximum number of records to retrieve (default: 100)

  • offset - Number of records to skip (default: 0)

  • order - Hyper Estraier expression to sort the results (example: @title STRA, default: ordering by score)

  • attributes - String to append to Hyper Estraier search query

  • raw_matches - Returns raw Hyper Estraier documents instead of instantiated AR objects

  • find - Options to pass on to the ActiveRecord::Base#find call

Examples:

Article.fulltext_search("biscuits AND gravy")
Article.fulltext_search("biscuits AND gravy", :limit => 15, :offset => 14)
Article.fulltext_search("biscuits AND gravy", :attributes => "tag STRINC food")
Article.fulltext_search("biscuits AND gravy", :attributes => ["tag STRINC food", "@title STRBW Biscuit"])
Article.fulltext_search("biscuits AND gravy", :order => "@title STRA")
Article.fulltext_search("biscuits AND gravy", :raw_matches => true)
Article.fulltext_search("biscuits AND gravy", :find => { :order => :title, :include => :comments })

Consult the Hyper Estraier documentation on proper query syntax:

http://hyperestraier.sourceforge.net/uguide-en.html#searchcond


172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/acts_as_searchable.rb', line 172

def fulltext_search(query = "", options = {})
  options.reverse_merge!(:limit => 100, :offset => 0)
  options.assert_valid_keys(VALID_FULLTEXT_OPTIONS)

  find_options = options[:find] || {}
  [ :limit, :offset ].each { |k| find_options.delete(k) } unless find_options.blank?

  cond = EstraierPure::Condition.new
  cond.set_phrase query
  cond.add_attr("type STREQ #{self.to_s}")
  [options[:attributes]].flatten.reject { |a| a.blank? }.each do |attr|
    cond.add_attr attr
  end
  cond.set_max   options[:limit]
  cond.set_skip  options[:offset]
  cond.set_order options[:order] if options[:order]

  matches = nil
  seconds = Benchmark.realtime do
    result = estraier_connection.search(cond, 1);
    return [] unless result
    
    matches = get_docs_from(result)
    return matches if options[:raw_matches]
  end

  logger.debug(
    connection.send(:format_log_entry, 
      "#{self.to_s} seach for '#{query}' (#{sprintf("%f", seconds)})",
      "Condition: #{cond.to_s}"
    )
  )
    
  matches.blank? ? [] : find(matches.collect { |m| m.attr('db_id') }, find_options)
end

#get_docs_from(result) ⇒ Object

:nodoc:



226
227
228
229
230
231
232
# File 'lib/acts_as_searchable.rb', line 226

def get_docs_from(result) #:nodoc:
  docs = []
  for i in 0...result.doc_num
    docs << result.get_doc(i)
  end
  docs
end

#reindex!Object

Peform a full re-index of the model data for this model



214
215
216
# File 'lib/acts_as_searchable.rb', line 214

def reindex!
  find(:all).each { |r| r.update_index(true) }
end