class NoBrainer::Criteria::Where::BinaryOperator
Public Class Methods
get_candidate_clauses(clauses, *types)
click to toggle source
# File lib/no_brainer/criteria/where.rb, line 70 def self.get_candidate_clauses(clauses, *types) clauses.select { |c| c.is_a?(self) && types.include?(c.op) } end
simplify_clauses(op, ast_clauses)
click to toggle source
# File lib/no_brainer/criteria/where.rb, line 74 def self.simplify_clauses(op, ast_clauses) # This code assumes that simplfy() has already been called on all clauses. if op == :or eq_clauses = get_candidate_clauses(ast_clauses, :in, :eq) new_clauses = eq_clauses.group_by { |c| [c.key_path, c.key_modifier] }.map do |(key_path, key_modifier), clauses| if key_modifier.in?([:scalar, :any]) && clauses.size > 1 values = clauses.flat_map { |c| c.op == :in ? c.value : [c.value] }.uniq [BinaryOperator.new(key_path, key_modifier, :in, values, clauses.first.model, true)] else clauses end end.flatten(1) if new_clauses.size != eq_clauses.size ast_clauses = ast_clauses - eq_clauses + new_clauses end end ast_clauses end
Public Instance Methods
compatible_with_index?(index)
click to toggle source
# File lib/no_brainer/criteria/where.rb, line 160 def compatible_with_index?(index) [key_modifier, index.multi].in?([[:any, true], [:scalar, false]]) end
simplify()
click to toggle source
# File lib/no_brainer/criteria/where.rb, line 95 def simplify new_key_path = cast_key_path(key_path) new_key_modifier, new_op, new_value = case op when :in then case value when Range then [key_modifier, :between, (cast_value(value.min)..cast_value(value.max))] when Array then [key_modifier, :in, value.map(&method(:cast_value)).uniq] else raise ArgumentError.new "`in' takes an array/range, not #{value}" end when :between then [key_modifier, op, (cast_value(value.min)..cast_value(value.max))] when :include then ensure_scalar_for(op); [:any, :eq, cast_value(value)] when :defined, :undefined then ensure_scalar_for(op); [key_modifier, op, cast_value(value)] when :during then [key_modifier, op, [cast_value(value.first), cast_value(value.last)]] else [key_modifier, op, cast_value(value)] end # When key_path relates to a polymorphic associatoin, the new_key_path is # an Array containing the foreign_type and then the foreign_key. if new_key_path.first.is_a?(Array) foreign_type, foreign_key = new_key_path.first MultiOperator.new( :and, [ BinaryOperator.new([foreign_type], new_key_modifier, new_op, value.class.to_s, model, true), BinaryOperator.new([foreign_key], new_key_modifier, new_op, value.__send__(value.class.pk_name), model, true) ] ) else BinaryOperator.new(new_key_path, new_key_modifier, new_op, new_value, model, true) end end
to_rql(doc)
click to toggle source
# File lib/no_brainer/criteria/where.rb, line 128 def to_rql(doc) key_path = [model.lookup_field_alias(self.key_path.first), *self.key_path[1..-1]] doc = key_path[0..-2].reduce(doc) { |d,k| d[k] } key = key_path.last case key_modifier when :scalar then case op when :defined then value ? doc.has_fields(key) : doc.has_fields(key).not when :undefined then !value ? doc.has_fields(key) : doc.has_fields(key).not else to_rql_scalar(doc[key]) end when :any then doc[key].map { |lvalue| to_rql_scalar(lvalue) }.contains(true) when :all then doc[key].map { |lvalue| to_rql_scalar(lvalue) }.contains(false).not end end
to_rql_scalar(lvalue)
click to toggle source
# File lib/no_brainer/criteria/where.rb, line 146 def to_rql_scalar(lvalue) case op when :between then (lvalue >= value.min) & (lvalue <= value.max) when :in then RethinkDB::RQL.new.expr(value).contains(lvalue) when :intersects then lvalue.intersects(value.to_rql) when :during then lvalue.during(value.first, value.last) when :near # XXX options[:max_results] is not used, seems to be a workaround of rethinkdb index implementation. circle = value[:circle] RethinkDB::RQL.new.distance(lvalue, circle.center.to_rql, circle.options) <= circle.radius else lvalue.__send__(op, value) end end
Private Instance Methods
cast_key_path(key_path)
click to toggle source
This method is used in order to transform association from the passed ‘key_path` in to model’s field(s).
When ‘key_path` contains a field, this method just ensures the given field is well defined in the owner model. When `key_path` contains the name of an association, this method updates the `key_path` in order to replace the association name with the field(s) behind that association.
In the case of :
-
a polymorphic association name passed as ‘key_path`, the association name will be replaced by the 2 fields representing it (foreign_key and foreign_type)
-
all other association the association name is replaced by the primary_key or the foreign_key depending on the association type
# File lib/no_brainer/criteria/where.rb, line 227 def cast_key_path(key_path) return key_path if casted_values # key_path is an Array of symbols representing the path to the key being # queried. # # The Array size can be greater that 1 when quering from a field with, # the type `Hash` and the query is targetting a nested key from the Hash. # # The first Array element is always the field name. if key_path.size == 1 # With fields and non-polymorphic associations `keys` will be a symbol # while with a polymorphic association it will be an Array of symbols # being the two fields used by the association. keys, _v = model.association_user_to_model_cast(key_path.first, nil, value.class) key_path = [keys] end # Ensures fields exist on the model model.ensure_valid_key!(key_path.first) key_path end
cast_value(value)
click to toggle source
# File lib/no_brainer/criteria/where.rb, line 170 def cast_value(value) return value if casted_values case op when :defined, :undefined then NoBrainer::Boolean.nobrainer_cast_user_to_model(value) when :intersects raise "Use a geo object with `intersects`" unless value.is_a?(NoBrainer::Geo::Base) value when :near # TODO enforce key is a geo type case value when Hash options = NoBrainer::Geo::Base.normalize_geo_options(value) options[:radius] = options.delete(:max_distance) if options[:max_distance] options[:radius] = options.delete(:max_dist) if options[:max_dist] options[:center] = options.delete(:point) if options[:point] unless options[:circle] unless options[:center] && options[:radius] raise "`near' takes something like {:center => P, :radius => d}" end { :circle => NoBrainer::Geo::Circle.new(options), :max_results => options[:max_results] } end when NoBrainer::Geo::Circle then { :circle => value } else raise "Incorrect use of `near': rvalue must be a hash or a circle" end else # 1) Box value in array if we have an any/all modifier # 2) Box value in hash if we have a nested query. box_value = key_modifier.in?([:any, :all]) || op == :include value = [value] if box_value k_v = key_path.reverse.reduce(value) { |v,k| {k => v} }.first k_v = model.association_user_to_model_cast(*k_v, value.class) value = model.cast_user_to_db_for(*k_v) value = key_path[1..-1].reduce(value) { |h,k| h[k] } value = value.first if box_value value end end
ensure_scalar_for(op)
click to toggle source
# File lib/no_brainer/criteria/where.rb, line 166 def ensure_scalar_for(op) raise "Incorrect use of `#{op}' and `#{key_modifier}'" if key_modifier != :scalar end