module Sunspot class Field #:nodoc: attr_accessor :name # The public-facing name of the field attr_accessor :type # The Type of the field attr_accessor :reference # Model class that the value of this field refers to attr_reader :boost attr_reader :indexed_name # Name with which this field is indexed internally. Based on public name and type or the +:as+ option. # # def initialize(name, type, options = {}) #:nodoc @name, @type = name.to_sym, type @stored = !!options.delete(:stored) @more_like_this = !!options.delete(:more_like_this) @multiple ||= false set_indexed_name(options) raise ArgumentError, "Field of type #{type} cannot be used for more_like_this" unless type.accepts_more_like_this? or !@more_like_this end # Convert a value to its representation for Solr indexing. This delegates # to the #to_indexed method on the field's type. # # ==== Parameters # # value:: Value to convert to Solr representation # # ==== Returns # # String:: Solr representation of the object # # ==== Raises # # ArgumentError:: # the value is an array, but this field does not allow multiple values # def to_indexed(value) if value.is_a? Array if multiple? value.map { |val| to_indexed(val) } else raise ArgumentError, "#{name} is not a multiple-value field, so it cannot index values #{value.inspect}" end else @type.to_indexed(value) end end # Cast the value into the appropriate Ruby class for the field's type # # ==== Parameters # # value:: Solr's representation of the value # # ==== Returns # # Object:: The cast value # def cast(value) @type.cast(value) end # # Whether this field accepts multiple values. # # ==== Returns # # Boolean:: True if this field accepts multiple values. # def multiple? !!@multiple end # # Whether this field can be used for more_like_this queries. # If true, the field is configured to store termVectors. # # ==== Returns # # Boolean:: True if this field can be used for more_like_this queries. # def more_like_this? !!@more_like_this end # # Whether the field was joined from another model. # # ==== Returns # # Boolean:: True if this field was joined from another model # def joined? !!@joined end # # Whether the field is stored or not. # # ==== Returns # # Boolean:: True if this field is a stored field # def stored? !!@stored end def hash indexed_name.hash end def eql?(field) field.is_a?(self.class) && indexed_name == field.indexed_name end alias_method :==, :eql? private # # Raise if an unknown option passed # def check_options(options) raise ArgumentError, "Unknown field option #{options.keys.first.inspect} provided for field #{name.inspect}" unless options.empty? end # # Determine the indexed name. If the :as option is given use that, otherwise # create the value based on the indexed_name of the type with additional # suffixes for multiple, stored, and more_like_this. # # ==== Returns # # String: The field's indexed name # def set_indexed_name(options) @indexed_name = if options[:as] options.delete(:as).to_s else name = options[:prefix] ? @name.to_s.sub(/^#{options[:prefix]}_/, '') : @name "#{@type.indexed_name(name)}#{'m' if multiple? }#{'s' if @stored}#{'v' if more_like_this?}" end end end # # FulltextField instances represent fields that are indexed as fulltext. # These fields are tokenized in the index, and can have boost applied to # them. They also always allow multiple values (since the only downside of # allowing multiple values is that it prevents the field from being sortable, # and sorting on tokenized fields is nonsensical anyway, there is no reason # to do otherwise). FulltextField instances always have the type TextType. # class FulltextField < Field #:nodoc: attr_reader :default_boost def initialize(name, options = {}) super(name, Type::TextType.instance, options) @multiple = true @boost = options.delete(:boost) @default_boost = options.delete(:default_boost) check_options(options) end def indexed_name "#{super}" end end # # AttributeField instances encapsulate non-tokenized attribute data. # AttributeFields can have any type except TextType, and can also have # a reference (for instantiated facets), optionally allow multiple values # (false by default), and can store their values (false by default). All # scoping, sorting, and faceting is done with attribute fields. # class AttributeField < Field #:nodoc: def initialize(name, type, options = {}) @multiple = !!options.delete(:multiple) super(name, type, options) @reference = if (reference = options.delete(:references)).respond_to?(:name) reference.name elsif reference.respond_to?(:to_sym) reference.to_sym end check_options(options) end end # # JoinField encapsulates attributes from referenced models. # Could be of any type # class JoinField < Field #:nodoc: attr_reader :default_boost, :target def initialize(name, type, options = {}) @multiple = !!options.delete(:multiple) super(name, type, options) @prefix = options.delete(:prefix) @join = options.delete(:join) @clazz = options.delete(:clazz) @target = options.delete(:target) @default_boost = options.delete(:default_boost) @joined = true check_options(options) end def from Sunspot::Setup.for(@target).field(@join[:from]).indexed_name end def to Sunspot::Setup.for(@clazz).field(@join[:to]).indexed_name end def local_params "{!join from=#{from} to=#{to}}" end def eql?(field) super && target == field.target && from == field.from && to == field.to end alias_method :==, :eql? end class TypeField #:nodoc: class <