module Sunspot # Sunspot provides an adapter architecture that allows applications or plugin # developers to define adapters for any type of object. An adapter is composed # of two classes, an InstanceAdapter and a DataAccessor. Note that an adapter # does not need to provide both classes - InstanceAdapter is only needed if # you wish to index instances of the class, and DataAccessor is only needed if # you wish to retrieve instances of this class in search results. Of course, # both will be the case most of the time. # # See Sunspot::Adapters::DataAccessor.register and # Sunspot::Adapters::InstanceAdapter.register for information on how to enable # an adapter for use by Sunspot. # # See spec/mocks/mock_adapter.rb for an example of how adapter classes should # be implemented. # module Adapters # Subclasses of the InstanceAdapter class should implement the #id method, # which returns the primary key of the instance stored in the @instance # variable. The primary key must be unique within the scope of the # instance's class. # # ==== Example: # # class FileAdapter < Sunspot::Adapters::InstanceAdapter # def id # File.expand_path(@instance.path) # end # end # # # then in your initializer # Sunspot::Adapters::InstanceAdapter.register(MyAdapter, File) # class InstanceAdapter def initialize(instance) #:nodoc: @instance = instance end # # The universally-unique ID for this instance that will be stored in solr # # ==== Returns # # String:: ID for use in Solr # def index_id #:nodoc: "#{@instance.class.name} #{id}" end class <<self # Instantiate an InstanceAdapter for the given object, searching for # registered adapters for the object's class. # # ==== Parameters # # instance<Object>:: The instance to adapt # # ==== Returns # # InstanceAdapter:: # An instance of an InstanceAdapter implementation that # wraps the given instance # def adapt(instance) #:nodoc: self.for(instance.class).new(instance) end # Register an instance adapter for a set of classes. When searching for # an adapter for a given instance, Sunspot starts with the instance's # class, and then searches for registered adapters up the class's # ancestor chain. # # ==== Parameters # # instance_adapter<Class>:: The instance adapter class to register # classes...<Class>:: # One or more classes that this instance adapter adapts # def register(instance_adapter, *classes) for clazz in classes instance_adapters[clazz.name.to_sym] = instance_adapter end end # Find the best InstanceAdapter implementation that adapts the given # class. Starting with the class and then moving up the ancestor chain, # looks for registered InstanceAdapter implementations. # # ==== Parameters # # clazz<Class>:: The class to find an InstanceAdapter for # # ==== Returns # # Class:: Subclass of InstanceAdapter, or nil if none found # def for(clazz) #:nodoc: while clazz != Object class_name = clazz.name.to_sym return instance_adapters[class_name] if instance_adapters[class_name] clazz = clazz.superclass end nil end protected # Lazy-initialize the hash of registered instance adapters # # ==== Returns # # Hash:: Hash containing class names keyed to instance adapter classes # def instance_adapters #:nodoc: @instance_adapters ||= {} end end end # Subclasses of the DataAccessor class take care of retreiving instances of # the adapted class from (usually persistent) storage. Subclasses must # implement the #load method, which takes an id (the value returned by # InstanceAdapter#id, as a string), and returns the instance referenced by # that ID. Optionally, it can also override the #load_all method, which # takes an array of IDs and returns an array of instances in the order # given. #load_all need only be implemented if it can be done more # efficiently than simply iterating over the IDs and calling #load on each # individually. # # ==== Example # # class FileAccessor < Sunspot::Adapters::InstanceAdapter # def load(id) # @clazz.open(id) # end # end # # Sunspot::Adapters::DataAccessor.register(FileAccessor, File) # class DataAccessor def initialize(clazz) #:nodoc: @clazz = clazz end # Subclasses can override this class to provide more efficient bulk # loading of instances. Instances must be returned in the same order # that the IDs were given. # # ==== Parameters # # ids<Array>:: collection of IDs # # ==== Returns # # Array:: collection of instances, in order of IDs given # def load_all(ids) ids.map { |id| self.load(id) } end class <<self # Create a DataAccessor for the given class, searching registered # adapters for the best match. See InstanceAdapter#adapt for discussion # of inheritence. # # ==== Parameters # # clazz<Class>:: Class to create DataAccessor for # # ==== Returns # # DataAccessor:: # DataAccessor implementation which provides access to given class # def create(clazz) self.for(clazz).new(clazz) end # Register data accessor for a set of classes. When searching for # an accessor for a given class, Sunspot starts with the class, # and then searches for registered adapters up the class's ancestor # chain. # # ==== Parameters # # data_accessor<Class>:: The data accessor class to register # classes...<Class>:: # One or more classes that this data accessor providess access to # def register(data_accessor, *classes) for clazz in classes data_accessors[clazz.name.to_sym] = data_accessor end end # Find the best DataAccessor implementation that adapts the given class. # Starting with the class and then moving up the ancestor chain, looks # for registered DataAccessor implementations. # # ==== Parameters # # clazz<Class>:: The class to find a DataAccessor for # # ==== Returns # # Class:: Implementation of DataAccessor, or nil if none found # def for(clazz) #:nodoc: while clazz != Object class_name = clazz.name.to_sym return data_accessors[class_name] if data_accessors[class_name] clazz = clazz.superclass end end protected # Lazy-initialize the hash of registered data accessors # # ==== Returns # # Hash:: Hash containing class names keyed to data accessor classes # def data_accessors #:nodoc: @adapters ||= {} end end end end end