# 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