# encoding: utf-8
module Mongoid #:nodoc:
module Contexts #:nodoc:
class Mongo
include Ids, Paging
attr_reader :criteria
delegate :klass, :options, :selector, :to => :criteria
# Aggregate the context. This will take the internally built selector and options
# and pass them on to the Ruby driver's +group()+ method on the collection. The
# collection itself will be retrieved from the class provided, and once the
# query has returned it will provided a grouping of keys with counts.
#
# Example:
#
# context.aggregate
#
# Returns:
#
# A +Hash+ with field values as keys, counts as values
def aggregate
klass.collection.group(options[:fields], selector, { :count => 0 }, Javascript.aggregate, true)
end
# Determine if the context is empty or blank given the criteria. Will
# perform a quick has_one asking only for the id.
#
# Example:
#
# context.blank?
def blank?
klass.collection.find_one(selector, { :fields => [ :_id ] }).nil?
end
alias :empty? :blank?
# Get the count of matching documents in the database for the context.
#
# Example:
#
# context.count
#
# Returns:
#
# An +Integer+ count of documents.
def count
@count ||= klass.collection.find(selector, process_options).count
end
# Execute the context. This will take the selector and options
# and pass them on to the Ruby driver's +find()+ method on the collection. The
# collection itself will be retrieved from the class provided, and once the
# query has returned new documents of the type of class provided will be instantiated.
#
# Example:
#
# context.execute
#
# Returns:
#
# An enumerable +Cursor+.
def execute(paginating = false)
cursor = klass.collection.find(selector, process_options)
if cursor
@count = cursor.count if paginating
cursor
else
[]
end
end
# Groups the context. This will take the internally built selector and options
# and pass them on to the Ruby driver's +group()+ method on the collection. The
# collection itself will be retrieved from the class provided, and once the
# query has returned it will provided a grouping of keys with objects.
#
# Example:
#
# context.group
#
# Returns:
#
# A +Hash+ with field values as keys, arrays of documents as values.
def group
klass.collection.group(
options[:fields],
selector,
{ :group => [] },
Javascript.group,
true
).collect do |docs|
docs["group"] = docs["group"].collect do |attrs|
Mongoid::Factory.build(klass, attrs)
end
docs
end
end
# Create the new mongo context. This will execute the queries given the
# selector and options against the database.
#
# Example:
#
# Mongoid::Contexts::Mongo.new(criteria)
def initialize(criteria)
@criteria = criteria
if klass.hereditary && Mongoid.persist_types
criteria.in(:_type => criteria.klass._types)
end
criteria.enslave if klass.enslaved?
criteria.cache if klass.cached?
end
# Iterate over each +Document+ in the results. This can take an optional
# block to pass to each argument in the results.
#
# Example:
#
# context.iterate { |doc| p doc }
def iterate(&block)
return caching(&block) if criteria.cached?
if block_given?
execute.each { |doc| yield doc }
end
end
# Return the last result for the +Context+. Essentially does a find_one on
# the collection with the sorting reversed. If no sorting parameters have
# been provided it will default to ids.
#
# Example:
#
# context.last
#
# Returns:
#
# The last document in the collection.
def last
opts = process_options
sorting = opts[:sort]
sorting = [[:_id, :asc]] unless sorting
opts[:sort] = sorting.collect { |option| [ option[0], option[1].invert ] }
attributes = klass.collection.find_one(selector, opts)
attributes ? Mongoid::Factory.build(klass, attributes) : nil
end
# Return the max value for a field.
#
# This will take the internally built selector and options
# and pass them on to the Ruby driver's +group()+ method on the collection. The
# collection itself will be retrieved from the class provided, and once the
# query has returned it will provided a grouping of keys with sums.
#
# Example:
#
# context.max(:age)
#
# Returns:
#
# A numeric max value.
def max(field)
grouped(:max, field.to_s, Javascript.max)
end
# Return the min value for a field.
#
# This will take the internally built selector and options
# and pass them on to the Ruby driver's +group()+ method on the collection. The
# collection itself will be retrieved from the class provided, and once the
# query has returned it will provided a grouping of keys with sums.
#
# Example:
#
# context.min(:age)
#
# Returns:
#
# A numeric minimum value.
def min(field)
grouped(:min, field.to_s, Javascript.min)
end
# Return the first result for the +Context+.
#
# Example:
#
# context.one
#
# Return:
#
# The first document in the collection.
def one
attributes = klass.collection.find_one(selector, process_options)
attributes ? Mongoid::Factory.build(klass, attributes) : nil
end
alias :first :one
# Sum the context.
#
# This will take the internally built selector and options
# and pass them on to the Ruby driver's +group()+ method on the collection. The
# collection itself will be retrieved from the class provided, and once the
# query has returned it will provided a grouping of keys with sums.
#
# Example:
#
# context.sum(:age)
#
# Returns:
#
# A numeric value that is the sum.
def sum(field)
grouped(:sum, field.to_s, Javascript.sum)
end
# Common functionality for grouping operations. Currently used by min, max
# and sum. Will gsub the field name in the supplied reduce function.
def grouped(start, field, reduce)
collection = klass.collection.group(
nil,
selector,
{ start => "start" },
reduce.gsub("[field]", field),
true
)
collection.empty? ? nil : collection.first[start.to_s]
end
# Filters the field list. If no fields have been supplied, then it will be
# empty. If fields have been defined then _type will be included as well.
def process_options
fields = options[:fields]
if fields && fields.size > 0 && !fields.include?(:_type)
fields << :_type
options[:fields] = fields
end
options.dup
end
protected
# Iterate over each +Document+ in the results and cache the collection.
def caching(&block)
if defined? @collection
@collection.each(&block)
else
@collection = []
execute.each do |doc|
@collection << doc
yield doc if block_given?
end
end
end
end
end
end