lib/passive_record/core/query.rb
module PassiveRecord
module Core
class Query
include Enumerable
extend Forwardable
include PassiveRecord::ArithmeticHelpers
attr_reader :conditions
def initialize(klass,conditions={},scope=nil)
@klass = klass
@conditions = conditions
@scope = scope
end
def not(new_conditions={})
NegatedQuery.new(@klass, new_conditions)
end
def or(query=nil)
DisjoinedQuery.new(@klass, self, query)
end
def all
if @scope
matching = @scope.method(:matching_instances)
if negated?
raw_all.reject(&matching)
else
raw_all.select(&matching)
end
else
matching = method(:matching_instances)
raw_all.select(&matching)
end
end
def_delegators :all, :sample, :uniq, :count
def raw_all
@klass.all
end
def each
if @scope
matching = @scope.method(:matching_instances)
if negated?
raw_all.each do |instance|
yield instance unless matching[instance]
end
else
raw_all.each do |instance|
yield instance if matching[instance]
end
end
else
matching = method(:matching_instances)
@klass.all.each do |instance|
yield instance if matching[instance]
end
end
end
def matching_instances(instance)
@conditions.all? do |(field,value)|
evaluate_condition(instance, field, value)
end
end
def create(attrs={})
@klass.create(@conditions.merge(attrs))
end
def first_or_create(*args)
q = where(*args)
q.first || q.create
end
def where(new_conditions={})
@conditions.merge!(new_conditions)
self
end
def negated?
false
end
def disjoined?
false
end
def conjoined?
false
end
def basic?
!negated? && !disjoined? && !conjoined?
end
def and(scope_query)
ConjoinedQuery.new(@klass, self, scope_query)
end
def method_missing(meth,*args,&blk)
if @klass.methods.include?(meth)
scope_query = @klass.send(meth,*args,&blk)
if negated? && @scope.nil? && @conditions.empty?
@scope = scope_query
self
elsif basic? && scope_query.basic?
@conditions.merge!(scope_query.conditions)
self
else
scope_query.and(self)
end
else
super(meth,*args,&blk)
end
end
protected
def evaluate_condition(instance, field, value)
case value
when Hash then evaluate_nested_conditions(instance, field, value)
when Range then value.cover?(instance.send(field))
when Array then value.include?(instance.send(field))
else
instance.send(field) == value
end
end
def evaluate_nested_conditions(instance, field, value)
association = instance.send(field)
association && value.all? do |(association_field,val)|
if association.is_a?(Associations::Relation) && !association.singular?
association.where(association_field => val).any?
elsif val.is_a?(Hash)
evaluate_nested_conditions(association, association_field, val)
else
association.send(association_field) == val
end
end
end
end
class NegatedQuery < Query
def matching_instances(instance)
@conditions.none? do |(field,value)|
evaluate_condition(instance, field, value)
end
end
def negated?
true
end
end
class DisjoinedQuery < Query
def initialize(klass, first_query, second_query, conditions={})
@klass = klass
@first_query = first_query
@second_query = second_query
@conditions = conditions
end
def all
(@first_query.where(conditions).all + @second_query.where(conditions).all).uniq
end
def disjoined?
true
end
end
class ConjoinedQuery < Query
def initialize(klass, first_query, second_query, conditions={})
@klass = klass
@first_query = first_query
@second_query = second_query
@conditions = conditions
end
def all
@first_query.where(conditions).all & @second_query.all
end
def conjoined?
true
end
end
class HasManyThroughQuery < Query
def initialize(klass, instance, target_name_sym, conditions={})
@klass = klass
@instance = instance
@target_name_sym = target_name_sym
@conditions = conditions
end
def raw_all
@instance.send(@target_name_sym).all
end
end
end
end