module Sunspot module Query module Restriction #:nodoc: class <<self # # Return the names of all of the restriction classes that should be made # available to the DSL. # # ==== Returns # # Array:: Collection of restriction class names # def names constants - %w(Base) #XXX this seems ugly end # # Convenience method to access a restriction class by an underscored # symbol or string # def [](restriction_name) @types ||= {} @types[restriction_name.to_sym] ||= const_get(Sunspot::Util.camel_case(restriction_name.to_s)) end end # # Subclasses of this class represent restrictions that can be applied to # a Sunspot query. The Sunspot::DSL::Restriction class presents a builder # API for instances of this class. # # Implementations of this class must respond to #to_params and # #to_negated_params. Instead of implementing those methods, they may # choose to implement any of: # # * #to_positive_boolean_phrase, and optionally #to_negated_boolean_phrase # * #to_solr_conditional # class Base #:nodoc: include Filter include RSolr::Char RESERVED_WORDS = Set['AND', 'OR', 'NOT'] def initialize(field, value, negated = false) @field, @value, @negated = field, value, negated end # # A hash representing this restriction in solr-ruby's parameter format. # All restriction implementations must respond to this method; however, # the base implementation delegates to the #to_positive_boolean_phrase method, so # subclasses may (and probably should) choose to implement that method # instead. # # ==== Returns # # Hash:: Representation of this restriction as solr-ruby parameters # def to_params { :fq => [to_filter_query] } end # # Return the boolean phrase associated with this restriction object. # Differentiates between positive and negated boolean phrases depending # on whether this restriction is negated. # def to_boolean_phrase unless negated? to_positive_boolean_phrase else to_negated_boolean_phrase end end # # Boolean phrase representing this restriction in the positive. Subclasses # may choose to implement this method rather than #to_params; however, # this method delegates to the abstract #to_solr_conditional method, which # in most cases will be what subclasses will want to implement. # #to_solr_conditional contains the boolean phrase representing the # condition but leaves out the field name (see built-in implementations # for examples) # # ==== Returns # # String:: Boolean phrase for restriction in the positive # def to_positive_boolean_phrase "#{escape(@field.indexed_name)}:#{to_solr_conditional}" end # # Boolean phrase representing this restriction in the negated. Subclasses # may choose to implement this method, but it is not necessary, as the # base implementation delegates to #to_positive_boolean_phrase. # # ==== Returns # # String:: Boolean phrase for restriction in the negated # def to_negated_boolean_phrase "-#{to_positive_boolean_phrase}" end # # Whether this restriction should be negated from its original meaning # def negated? #:nodoc: !!@negated end # # Return a new restriction that is the negated version of this one. It # is used by disjunction denormalization. # def negate self.class.new(@field, @value, !@negated) end protected # # Return escaped Solr API representation of given value # # ==== Parameters # # value<Object>:: # value to convert to Solr representation (default: @value) # # ==== Returns # # String:: Solr API representation of given value # def solr_value(value = @value) solr_value = escape(@field.to_indexed(value)) if RESERVED_WORDS.include?(solr_value) %Q("#{solr_value}") else solr_value end end end # # Results must have field with value equal to given value. If the value # is nil, results must have no value for the given field. # class EqualTo < Base def to_positive_boolean_phrase unless @value.nil? super else "#{escape(@field.indexed_name)}:[* TO *]" end end def negated? if @value.nil? !super else super end end private def to_solr_conditional "#{solr_value}" end end # # Results must have field with value less than given value # class LessThan < Base private def solr_value(value = @value) solr_value = super solr_value = "\"#{solr_value}\"" if solr_value.index(' ') solr_value end def to_solr_conditional "[* TO #{solr_value}]" end end # # Results must have field with value greater than given value # class GreaterThan < Base private def solr_value(value = @value) solr_value = super solr_value = "\"#{solr_value}\"" if solr_value.index(' ') solr_value end def to_solr_conditional "[#{solr_value} TO *]" end end # # Results must have field with value in given range # class Between < Base private def solr_value(value = @value) solr_value = super solr_value = "\"#{solr_value}\"" if solr_value.index(' ') solr_value end def to_solr_conditional first, last = [@value.first, @value.last].sort "[#{solr_value(first)} TO #{solr_value(last)}]" end end # # Results must have field with value included in given collection # class AnyOf < Base private def to_solr_conditional "(#{@value.map { |v| solr_value v } * ' OR '})" end end # # Results must have field with values matching all values in given # collection (only makes sense for fields with multiple values) # class AllOf < Base private def to_solr_conditional "(#{@value.map { |v| solr_value v } * ' AND '})" end end # # Results must have a field with a value that begins with the argument. # Most useful for strings, but in theory will work with anything. # class StartingWith < Base private def to_solr_conditional "#{solr_value(@value)}*" end end end end end