# encoding: utf-8
module Mongoid #:nodoc:
module Contexts #:nodoc:
class Mongo
include Ids, Paging
attr_accessor :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
# Get the average value for the supplied 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 averages.
#
# Example:
#
# context.avg(:age)
#
# Returns:
#
# A numeric value that is the average.
def avg(field)
total = sum(field)
total ? (total / count) : nil
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
# Delete all the documents in the database matching the selector.
#
# @example Delete the documents.
# context.delete_all
#
# @return [ Integer ] The number of documents deleted.
#
# @since 2.0.0.rc.1
def delete_all
klass.delete_all(:conditions => selector)
end
alias :delete :delete_all
# Destroy all the documents in the database matching the selector.
#
# @example Destroy the documents.
# context.destroy_all
#
# @return [ Integer ] The number of documents destroyed.
#
# @since 2.0.0.rc.1
def destroy_all
klass.destroy_all(:conditions => selector)
end
alias :destroy :destroy_all
# Gets an array of distinct values for the supplied field across the
# entire collection or the susbset given the criteria.
#
# Example:
#
# context.distinct(:title)
def distinct(field)
klass.collection.distinct(field, selector)
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
).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? && !criteria.selector.keys.include?(:_type)
@criteria = 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
# Return the first result for the +Context+ and skip it
# for successive calls.
#
# Returns:
#
# The first document in the collection.
def shift
document = first
criteria.skip((options[:skip] || 0) + 1)
document
end
# 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)
)
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
# Very basic update that will perform a simple atomic $set of the
# attributes provided in the hash. Can be expanded to later for more
# robust functionality.
#
# @example Update all matching documents.
# context.update_all(:title => "Sir")
#
# @param [ Hash ] attributes The sets to perform.
#
# @since 2.0.0.rc.4
def update_all(attributes = {})
klass.collection.update(
selector,
{ "$set" => attributes },
:multi => true,
:safe => Mongoid.persist_in_safe_mode
)
end
alias :update :update_all
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