require 'bson' module Sunspot #:nodoc: module Rails #:nodoc: # # This module adds Sunspot functionality to Mongoid models. As well as # providing class and instance methods, it optionally adds lifecycle hooks # to automatically add and remove models from the Solr index as they are # created and destroyed. # module Searchable class <Sunspot.setup method. See the Sunspot documentation for # complete information on the functionality provided by that method. # # ==== Options (+options+) # # :auto_index:: # Automatically index models in Solr when they are saved. # Default: true # :auto_remove:: # Automatically remove models from the Solr index when they are # destroyed. Setting this option to +false+ is not recommended # (see the README). # :ignore_attribute_changes_of:: # Define attributes, that should not trigger a reindex of that # object. Usual suspects are updated_at or counters. # # ==== Example # # class Post # include Sunspot::Rails::Mongoid # searchable do # text :title, :body # string :sort_title do # title.downcase.sub(/^(an?|the)/, '') # end # integer :blog_id # time :updated_at # end # end # def searchable(options = {}, &block) Sunspot.setup(self, &block) extend ClassMethods include InstanceMethods class_inheritable_hash :sunspot_options unless options[:auto_index] == false before_save :maybe_mark_for_auto_indexing after_save :maybe_auto_index end unless options[:auto_remove] == false after_destroy do |searchable| searchable.remove_from_index end end self.sunspot_options = options end # # This is left for compatibility reasons. for Mongoid, the searchable # class method gets defined only when the ::Mongoid document is included. # At that point, searchable? will return true # # ==== Returns # # +false+ # def searchable? false end end module ClassMethods def self.extended(base) #:nodoc: class < 1, :per_page => count) }.to_set only(:id).each do |object| indexed_ids.delete(object.id) end indexed_ids.to_a end # # Find IDs of records of this class that are indexed in Solr but do not # exist in the database, and remove them from Solr. Under normal # circumstances, this should not be necessary; this method is provided # in case something goes wrong. # def solr_clean_index_orphans solr_index_orphans.each do |id| new do |fake_instance| fake_instance.id = id end.solr_remove_from_index end end # # Classes that have been defined as searchable return +true+ for this # method. # # ==== Returns # # +true+ # def searchable? true end def solr_execute_search(options = {}) options.assert_valid_keys(:select) search = yield unless options.empty? search.build do |query| if options[:select] query.data_accessor_for(self).select = options[:select] end end end search.execute end def solr_execute_search_ids(options = {}) search = yield search.raw_results.map { |raw_result| BSON::ObjectId.from_string(raw_result.primary_key) } end protected # # Does some logging for benchmarking indexing performance # def solr_benchmark(batch_size, counter, &block) start = Time.now logger.info("[#{Time.now}] Start Indexing") yield elapsed = Time.now-start logger.info("[#{Time.now}] Completed Indexing. Rows indexed #{counter * batch_size}. Rows/sec: #{batch_size/elapsed.to_f} (Elapsed: #{elapsed} sec.)") end end module InstanceMethods def self.included(base) #:nodoc: base.module_eval do alias_method :index, :solr_index unless method_defined? :index alias_method :index!, :solr_index! unless method_defined? :index! alias_method :remove_from_index, :solr_remove_from_index unless method_defined? :remove_from_index alias_method :remove_from_index!, :solr_remove_from_index! unless method_defined? :remove_from_index! alias_method :more_like_this, :solr_more_like_this unless method_defined? :more_like_this alias_method :more_like_this_ids, :solr_more_like_this_ids unless method_defined? :more_like_this_ids end end # # Index the model in Solr. If the model is already indexed, it will be # updated. Using the defaults, you will usually not need to call this # method, as models are indexed automatically when they are created or # updated. If you have disabled automatic indexing (see # ClassMethods#searchable), this method allows you to manage indexing # manually. # def solr_index Sunspot.index(self) end # # Index the model in Solr and immediately commit. See #index # def solr_index! Sunspot.index!(self) end # # Remove the model from the Solr index. Using the defaults, this should # not be necessary, as models will automatically be removed from the # index when they are destroyed. If you disable automatic removal # (which is not recommended!), you can use this method to manage removal # manually. # def solr_remove_from_index Sunspot.remove(self) end # # Remove the model from the Solr index and commit immediately. See # #remove_from_index # def solr_remove_from_index! Sunspot.remove!(self) end def solr_more_like_this(*args, &block) options = args.extract_options! self.class.solr_execute_search(options) do Sunspot.new_more_like_this(self, *args, &block) end end def solr_more_like_this_ids(&block) self.class.solr_execute_search_ids do solr_more_like_this(&block) end end private def maybe_mark_for_auto_indexing @marked_for_auto_indexing = if !new_record? && ignore_attributes = self.class.sunspot_options[:ignore_attribute_changes_of] @marked_for_auto_indexing = !(changed.map { |attr| attr.to_sym } - ignore_attributes).blank? else true end true end def maybe_auto_index if @marked_for_auto_indexing solr_index remove_instance_variable(:@marked_for_auto_indexing) end end end end end end