# encoding: utf-8 require 'mongoid/contexts/enumerable/sort' module Mongoid #:nodoc: module Contexts #:nodoc: class Enumerable include Ids, Paging attr_accessor :criteria delegate :blank?, :empty?, :first, :last, :to => :execute delegate :klass, :documents, :options, :selector, :to => :criteria # Return aggregation counts of the grouped documents. This will count by # the first field provided in the fields array. # # Returns: # # A +Hash+ with field values as keys, count as values def aggregate counts = {} group.each_pair { |key, value| counts[key] = value.size } counts end # Get the average value for the supplied field. # # Example: # # context.avg(:age) # # Returns: # # A numeric value that is the average. def avg(field) total = sum(field) total ? (total.to_f / count) : nil end # Gets the number of documents in the array. Delegates to size. def count @count ||= filter.size 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 count.tap { filter.each(&:delete) } 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 count.tap { filter.each(&:destroy) } end alias :destroy :destroy_all # Gets an array of distinct values for the supplied field across the # entire array or the susbset given the criteria. # # Example: # # context.distinct(:title) def distinct(field) execute.collect { |doc| doc.send(field) }.uniq end # Enumerable implementation of execute. Returns matching documents for # the selector, and adds options if supplied. # # Returns: # # An +Array+ of documents that matched the selector. def execute(paginating = false) limit(sort(filter)) || [] end # Groups the documents by the first field supplied in the field options. # # Returns: # # A +Hash+ with field values as keys, arrays of documents as values. def group field = options[:fields].first execute.group_by { |doc| doc.send(field) } end # Create the new enumerable context. This will need the selector and # options from a +Criteria+ and a documents array that is the underlying # array of embedded documents from a has many association. # # Example: # # Mongoid::Contexts::Enumerable.new(criteria) def initialize(criteria) @criteria = criteria 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) execute.each(&block) end # Get the largest value for the field in all the documents. # # Returns: # # The numerical largest value. def max(field) determine(field, :>=) end # Get the smallest value for the field in all the documents. # # Returns: # # The numerical smallest value. def min(field) determine(field, :<=) end # Get one document. # # Returns: # # The first document in the +Array+ alias :one :first # Get one document and tell the criteria to skip this record on # successive calls. # # Returns: # # The first document in the +Array+ def shift first.tap do |document| self.criteria = criteria.skip((options[:skip] || 0) + 1) end end # Get the sum of the field values for all the documents. # # Returns: # # The numerical sum of all the document field values. def sum(field) sum = execute.inject(nil) do |memo, doc| value = doc.send(field) memo ? memo += value : value end 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.6 def update_all(attributes = nil) iterate do |doc| doc.update_attributes(attributes || {}) end end alias :update :update_all protected # Filters the documents against the criteria's selector def filter documents.select { |document| document.matches?(selector) } end # If the field exists, perform the comparison and set if true. def determine(field, operator) matching = documents.inject(nil) do |memo, doc| value = doc.send(field) (memo && memo.send(operator, value)) ? memo : value end end # Limits the result set if skip and limit options. def limit(documents) skip, limit = options[:skip], options[:limit] if skip && limit return documents.slice(skip, limit) elsif limit return documents.first(limit) elsif skip return documents.slice(skip..-1) end documents end # Sorts the result set if sort options have been set. def sort(documents) return documents if options[:sort].blank? documents.sort_by do |document| options[:sort].map do |key, direction| Sort.new(document.read_attribute(key), direction) end end end end end end