mongodoc 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. data/Rakefile +21 -0
  2. data/TODO +6 -1
  3. data/VERSION +1 -1
  4. data/features/finders.feature +1 -1
  5. data/features/mongodoc_base.feature +11 -2
  6. data/features/{named_scopes.feature → scopes.feature} +0 -0
  7. data/features/step_definitions/document_steps.rb +15 -4
  8. data/features/step_definitions/documents.rb +3 -3
  9. data/features/step_definitions/query_steps.rb +17 -14
  10. data/features/step_definitions/{named_scope_steps.rb → scope_steps.rb} +0 -0
  11. data/features/using_criteria.feature +22 -43
  12. data/lib/mongodoc.rb +1 -1
  13. data/lib/mongodoc/associations/collection_proxy.rb +3 -1
  14. data/lib/mongodoc/associations/document_proxy.rb +4 -1
  15. data/lib/mongodoc/associations/hash_proxy.rb +3 -1
  16. data/lib/mongodoc/associations/proxy_base.rb +6 -4
  17. data/lib/mongodoc/attributes.rb +6 -6
  18. data/lib/mongodoc/contexts.rb +24 -0
  19. data/lib/mongodoc/contexts/enumerable.rb +132 -0
  20. data/lib/mongodoc/contexts/mongo.rb +215 -0
  21. data/lib/mongodoc/criteria.rb +36 -479
  22. data/lib/mongodoc/document.rb +3 -2
  23. data/lib/mongodoc/finders.rb +31 -11
  24. data/lib/mongodoc/matchers.rb +35 -0
  25. data/lib/mongodoc/scope.rb +64 -0
  26. data/lib/mongoid/contexts/paging.rb +42 -0
  27. data/lib/mongoid/criteria.rb +264 -0
  28. data/lib/mongoid/criterion/complex.rb +21 -0
  29. data/lib/mongoid/criterion/exclusion.rb +65 -0
  30. data/lib/mongoid/criterion/inclusion.rb +92 -0
  31. data/lib/mongoid/criterion/optional.rb +136 -0
  32. data/lib/mongoid/extensions/hash/criteria_helpers.rb +20 -0
  33. data/lib/mongoid/extensions/symbol/inflections.rb +36 -0
  34. data/lib/mongoid/matchers/all.rb +11 -0
  35. data/lib/mongoid/matchers/default.rb +26 -0
  36. data/lib/mongoid/matchers/exists.rb +13 -0
  37. data/lib/mongoid/matchers/gt.rb +11 -0
  38. data/lib/mongoid/matchers/gte.rb +11 -0
  39. data/lib/mongoid/matchers/in.rb +11 -0
  40. data/lib/mongoid/matchers/lt.rb +11 -0
  41. data/lib/mongoid/matchers/lte.rb +11 -0
  42. data/lib/mongoid/matchers/ne.rb +11 -0
  43. data/lib/mongoid/matchers/nin.rb +11 -0
  44. data/lib/mongoid/matchers/size.rb +11 -0
  45. data/mongodoc.gemspec +39 -9
  46. data/spec/attributes_spec.rb +16 -2
  47. data/spec/contexts/enumerable_spec.rb +335 -0
  48. data/spec/contexts/mongo_spec.rb +148 -0
  49. data/spec/contexts_spec.rb +28 -0
  50. data/spec/criteria_spec.rb +15 -766
  51. data/spec/finders_spec.rb +28 -36
  52. data/spec/matchers_spec.rb +342 -0
  53. data/spec/scope_spec.rb +79 -0
  54. metadata +40 -10
  55. data/features/step_definitions/criteria_steps.rb +0 -42
  56. data/lib/mongodoc/named_scope.rb +0 -68
  57. data/spec/named_scope_spec.rb +0 -82
data/Rakefile CHANGED
@@ -81,6 +81,27 @@ namespace :mongo do
81
81
  end
82
82
  end
83
83
 
84
+ namespace :mongoid do
85
+ desc 'Sync criteria from Mongoid'
86
+ task :sync do
87
+ require 'pathname'
88
+
89
+ src_dir = Pathname.new('../durran-mongoid/lib/mongoid')
90
+ dest_dir = Pathname.new('lib/mongoid')
91
+ dest_dir.mkpath
92
+ %w(criteria.rb contexts/paging.rb criterion extensions/symbol/inflections.rb extensions/hash/criteria_helpers.rb matchers).each do |f|
93
+ src = src_dir + f
94
+ if src.directory?
95
+ FileUtils.cp_r(src, dest_dir)
96
+ else
97
+ dest = dest_dir + f
98
+ dest.dirname.mkpath
99
+ FileUtils.cp(src, dest)
100
+ end
101
+ end
102
+ end
103
+ end
104
+
84
105
  namespace :bench do
85
106
  desc 'Run benchmark for MongoDoc'
86
107
  task 'mongodoc' do
data/TODO CHANGED
@@ -1,10 +1,15 @@
1
- As of 2010-02-05
1
+ As of 2010-02-23
2
2
 
3
3
  Associations
4
4
  ------------
5
5
 
6
6
  Pull associations out of attributes and refactor
7
7
 
8
+ Criteria
9
+ --------
10
+
11
+ Make critieria module, include into Document, CriteriaProxy, Association?
12
+
8
13
  Documentation
9
14
  -------------
10
15
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.2
1
+ 0.2.4
@@ -52,7 +52,7 @@ Feature: Finders
52
52
  And I save the document 'contractor'
53
53
 
54
54
  Scenario: All
55
- When I query contacts with all
55
+ When I query contacts with find_all
56
56
  Then the query result has 3 documents
57
57
 
58
58
  Scenario: Count
@@ -109,11 +109,20 @@ Feature: MongoDoc::Base
109
109
  Then the Place collection should have 1 document
110
110
  And the document 'hashrocket' roundtrips
111
111
 
112
- Scenario: Finder
112
+ Scenario: Class criteria
113
113
  Given an empty Contact document collection
114
114
  And a Contact document named 'hashrocket' :
115
115
  | Name | Type |
116
116
  | Hashrocket | company |
117
117
  And I save the last document
118
- When I query contacts with find {:where => {'type' => 'company'}}
118
+ When I query contacts with criteria where('type' => 'company')
119
+ Then the size of the last return value is 1
120
+
121
+ Scenario: Finder
122
+ Given an empty Contact document collection
123
+ And a Contact document named 'hashrocket' :
124
+ | Name | Type |
125
+ | Hashrocket | company |
126
+ And I save the document 'hashrocket'
127
+ When I find a contact using the id of 'hashrocket'
119
128
  Then the size of the last return value is 1
@@ -42,7 +42,7 @@ end
42
42
 
43
43
  Given /^I set the id on the document '(.*)' to (.*)$/ do |doc_name, value|
44
44
  doc = instance_variable_get("@#{doc_name}")
45
- doc._id = Mongo::ObjectID.new([value.to_i])
45
+ doc._id = Mongo::ObjectID.from_string("%024x" % value.to_i(16))
46
46
  end
47
47
 
48
48
  Given /^'(.+)' has one (.+?) as (.+?) \(identified by '(.+)'\):$/ do |doc_name, class_name, assoc_name, var_name, table|
@@ -79,10 +79,21 @@ When /^I update the document '(.*)' with the hash named '(.*)'$/ do |doc_name, h
79
79
  @last_return = doc.update_attributes(attrs)
80
80
  end
81
81
 
82
- When /^I query (.*) with find (.*)$/ do |doc, criteria_text|
82
+ When /^I query (.*) with criteria (.*)$/ do |doc, criteria_text|
83
+ klass = doc.singularize.camelize
84
+ @query = @last_return = eval("#{klass}.criteria.#{criteria_text}")
85
+ end
86
+
87
+ When /^I query (.*) with the '(.*)' id$/ do |doc, name|
83
88
  klass = doc.singularize.camelize.constantize
84
- criteria = eval(criteria_text)
85
- @last_return = klass.find(:all, criteria)
89
+ doc = instance_variable_get("@#{name}")
90
+ @query = @last_return = klass.criteria.id(doc.id).entries
91
+ end
92
+
93
+ When /^I find a (.*) using the id of '(.*)'$/ do |type, doc_name|
94
+ klass = type.camelize.constantize
95
+ doc = instance_variable_get("@#{doc_name}")
96
+ @last_return = klass.find(doc.id)
86
97
  end
87
98
 
88
99
  When /^'(.+)' is the first (.+?) of '(.+)'$/ do |var_name, single_assoc, doc_name|
@@ -24,7 +24,7 @@ class Contact
24
24
  key :interests
25
25
  has_many :addresses
26
26
 
27
- named_scope :rubyists, :in => {:interests => ['ruby']}
28
- named_scope :contract_work, :in => {:interests => ['contract work']}
29
- named_scope :in_state, lambda {|state| { :where => {'addresses.state' => state}}}
27
+ scope :rubyists, any_in(:interests => ['ruby'])
28
+ scope :contract_work, any_in(:interests => ['contract work'])
29
+ scope :in_state, lambda {|state| where('addresses.state' => state)}
30
30
  end
@@ -6,19 +6,23 @@ def query(klass_name = nil)
6
6
  @query ||= klass(klass_name).criteria
7
7
  end
8
8
 
9
- Then /^the (first|last) query result is equal to the document '(.*)'$/ do |position, name|
10
- object = instance_variable_get("@#{name}")
11
- query.send(position).should == object
9
+ Then /^the query result is equal to the document '(.*)'$/ do |name|
10
+ doc = instance_variable_get("@#{name}")
11
+ query.should == doc
12
12
  end
13
13
 
14
14
  Then /^one of the query results is the document '(.*)'$/ do |name|
15
- object = instance_variable_get("@#{name}")
16
- query.any? {|doc| doc == object}
15
+ doc = instance_variable_get("@#{name}")
16
+ query.any? {|d| d == doc}.should be_true
17
17
  end
18
18
 
19
- Then /^the aggregate query result with "(.*)" == "(.*)" has a count of (.*)$/ do |key, value, count|
20
- result = query.aggregate
21
- result.find {|r| r.has_key?(key) and r[key] == value }['count'].should == count.to_i
19
+ Then /^the query result with "(.*)" == "(.*)" has a count of (.*)$/ do |key, value, count|
20
+ query.find {|r| r.has_key?(key) and r[key] == value }['count'].should == count.to_i
21
+ end
22
+
23
+ Then /^the query result with "([^\"]*)" == "([^\"]*)" has the document '(.*)'$/ do |key, value, name|
24
+ doc = instance_variable_get("@#{name}")
25
+ query.find {|r| r.has_key?(key) and r[key] == value }['group'].should include(doc)
22
26
  end
23
27
 
24
28
  Then /^the query result has (.*) documents*$/ do |count|
@@ -29,14 +33,13 @@ Then /^the query result has (.*) documents*$/ do |count|
29
33
  end
30
34
  end
31
35
 
32
- Then /^the size of the query result is (.*)$/ do |count|
33
- query.to_a.size.should == count.to_i
36
+ Then /^the (first|last) query result is the document '(.*)'$/ do |position, name|
37
+ doc = instance_variable_get("@#{name}")
38
+ query.entries.send(position).should == doc
34
39
  end
35
40
 
36
- Then /^the group query result with "([^\"]*)" == "([^\"]*)" has the document '(.*)'$/ do |key, value, name|
37
- object = instance_variable_get("@#{name}")
38
- result = query.group
39
- result.find {|r| r.has_key?(key) and r[key] == value }['group'].should include(object)
41
+ Then /^the size of the query result is (.*)$/ do |count|
42
+ query.to_a.size.should == count.to_i
40
43
  end
41
44
 
42
45
  Then /^the query result is the document '(.*)'$/ do |name|
@@ -74,76 +74,55 @@ Feature: MongoDoc::Base
74
74
  And I save the document 'jax'
75
75
 
76
76
  Scenario: Counting results
77
- When I query contacts with every "{'interests' => ['ruby', 'rails', 'employment']}"
77
+ When I query contacts with criteria all('interests' => ['ruby', 'rails', 'employment'])
78
78
  Then the query result has 2 documents
79
79
 
80
80
  Scenario: Finding contacts with interests in ruby and rails
81
- When I query contacts with every "{'interests' => ['ruby', 'rails', 'employment']}"
82
- Then the query result has 2 documents
83
- And one of the query results is the document 'rocketeer'
81
+ When I query contacts with criteria all('interests' => ['ruby', 'rails', 'employment'])
82
+ Then one of the query results is the document 'rocketeer'
84
83
 
85
- Scenario: Finding contacts with interests in restaurants and hotels
86
- When I query contacts with every "{'interests' => ['ruby', 'rails', 'employment']}"
87
- Then the query result has 2 documents
88
- And one of the query results is the document 'contractor'
84
+ Scenario: Finding contacts with interests in restaurants or hotels
85
+ When I query contacts with criteria in('interests' => ['restaurants', 'hotels'])
86
+ Then one of the query results is the document 'contractor'
89
87
 
90
88
  Scenario: Aggregating Places
91
- When I query places to select field "type"
92
- And I query places where "{'address.state' => 'FL'}"
93
- Then the aggregate query result with "type" == "hotel" has a count of 2
89
+ When I query places with criteria only('type').where('address.state' => 'FL').aggregate
90
+ Then the query result with "type" == "hotel" has a count of 2
94
91
 
95
92
  Scenario: Excluding places in Neptune Beach
96
- When I query places to select field "type"
97
- And I query places that excludes "{'address.city' => 'Neptune Beach'}"
98
- Then the aggregate query result with "type" == "hotel" has a count of 1
93
+ When I query places with criteria only('type').where('address.city' => 'Neptune Beach').aggregate
94
+ Then the query result with "type" == "hotel" has a count of 1
99
95
 
100
96
  Scenario: Using extras to limit results
101
- When I query contacts with every "{'interests' => ['ruby', 'rails', 'employment']}"
102
- And I set the query extras limit on contacts to 1
97
+ When I query contacts with criteria all('interests' => ['ruby', 'rails', 'employment']).limit(1)
103
98
  Then the size of the query result is 1
104
99
 
105
100
  Scenario: Finding the first result
106
- When I query contacts with every "{'interests' => ['ruby', 'rails', 'employment']}"
107
- Then the first query result is equal to the document 'hashrocket'
101
+ When I query contacts with criteria all('interests' => ['ruby', 'rails', 'employment']).first
102
+ Then the query result is equal to the document 'hashrocket'
108
103
 
109
104
  Scenario: Grouping places by type
110
- When I query places to select field "type"
111
- And I query places where "{'type' => 'hotel'}"
112
- Then the group query result with "type" == "hotel" has the document 'one_ocean'
105
+ When I query places with criteria only('type').where('type' => 'hotel').group
106
+ Then the query result with "type" == "hotel" has the document 'one_ocean'
113
107
 
114
108
  Scenario: Selecting contacts with in operator
115
- When I query contacts with in "{'interests' => ['ruby', 'rails', 'employment']}"
109
+ When I query contacts with criteria in('interests' => ['ruby', 'rails', 'employment'])
116
110
  Then the query result has 3 documents
117
111
 
118
112
  Scenario: Selecting a contact with the id operator
119
113
  When I query contacts with the 'hashrocket' id
120
114
  Then the query result has 1 documents
121
- And the first query result is equal to the document 'hashrocket'
122
-
123
- Scenario: Finding the last result
124
- When I query contacts with every "{'interests' => ['ruby', 'rails', 'employment']}"
125
- Then the last query result is equal to the document 'rocketeer'
126
-
127
- Scenario: Using limit on results
128
- When I query contacts with every "{'interests' => ['ruby', 'rails', 'employment']}"
129
- And I set the query on contacts to limit 1
130
- Then the size of the query result is 1
115
+ And the query result is the document 'hashrocket'
131
116
 
132
117
  Scenario: Selecting contacts with not in operator
133
- When I query contacts with not in "{'interests' => ['contract work', 'employment']}"
118
+ When I query contacts with criteria not_in('interests' => ['contract work', 'employment'])
134
119
  Then the query result has 0 documents
135
120
 
136
121
  Scenario: Ordering contacts
137
- When I query contacts with in "{'interests' => ['ruby', 'rails']}"
138
- And I order the contacts query by "[[:name, :asc]]"
139
- Then the first query result is equal to the document 'contractor'
140
- Then the last query result is equal to the document 'rocketeer'
122
+ When I query contacts with criteria in('interests' => ['ruby', 'rails']).order_by([[:name, :asc]]).entries
123
+ Then the first query result is the document 'contractor'
124
+ And the last query result is the document 'rocketeer'
141
125
 
142
126
  Scenario: Using skip on results
143
- When I query contacts with every "{'interests' => ['ruby', 'rails']}"
144
- And I set the query on contacts to skip 1
127
+ When I query contacts with criteria all('interests' => ['ruby', 'rails']).skip(1)
145
128
  Then the size of the query result is 2
146
-
147
- Scenario: All
148
- When I query contacts with 'all'
149
- Then the size of the query result is 3
@@ -11,7 +11,7 @@ require 'validatable'
11
11
  require 'will_paginate/collection'
12
12
 
13
13
  module MongoDoc
14
- VERSION = '0.2.2'
14
+ VERSION = '0.2.4'
15
15
  end
16
16
 
17
17
  require 'mongodoc/connection'
@@ -2,7 +2,9 @@
2
2
  # http://github.com/sandro
3
3
  module MongoDoc
4
4
  module Associations
5
- class CollectionProxy < ProxyBase
5
+ class CollectionProxy
6
+ include ProxyBase
7
+
6
8
  # List of array methods (that are not in +Object+) that need to be
7
9
  # delegated to +collection+.
8
10
  ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map { |n| n.to_s }
@@ -1,9 +1,12 @@
1
1
  module MongoDoc
2
2
  module Associations
3
- class DocumentProxy < ProxyBase
3
+ class DocumentProxy
4
+ include ProxyBase
4
5
 
5
6
  attr_reader :document
6
7
 
8
+ delegate :to_bson, :id, :to => :document
9
+
7
10
  def _root=(root)
8
11
  @_root = root
9
12
  document._root = root if is_document?(document)
@@ -2,7 +2,9 @@ module MongoDoc
2
2
  class InvalidEmbeddedHashKey < RuntimeError; end
3
3
 
4
4
  module Associations
5
- class HashProxy < ProxyBase
5
+ class HashProxy
6
+ include ProxyBase
7
+
6
8
  HASH_METHODS = (Hash.instance_methods - Object.instance_methods).map { |n| n.to_s }
7
9
 
8
10
  MUST_DEFINE = %w[to_a inspect to_bson ==]
@@ -1,9 +1,11 @@
1
1
  module MongoDoc
2
2
  module Associations
3
- class ProxyBase
4
- undef_method :id, :to_bson
5
-
6
- attr_reader :assoc_name, :assoc_class, :_parent, :_root
3
+ module ProxyBase
4
+ def self.included(klass)
5
+ klass.class_eval do
6
+ attr_reader :assoc_name, :assoc_class, :_parent, :_root
7
+ end
8
+ end
7
9
 
8
10
  def _parent=(parent)
9
11
  @_parent = parent
@@ -51,7 +51,7 @@ module MongoDoc
51
51
  def has_one(*args)
52
52
  options = args.extract_options!
53
53
  assoc_class = if class_name = options.delete(:class_name)
54
- type_name_with_module(class_name).constantize
54
+ self.class_from_name(class_name)
55
55
  end
56
56
 
57
57
  args.each do |name|
@@ -75,7 +75,7 @@ module MongoDoc
75
75
  def has_many(*args)
76
76
  options = args.extract_options!
77
77
  assoc_class = if class_name = options.delete(:class_name)
78
- type_name_with_module(class_name).constantize
78
+ self.class_from_name(class_name)
79
79
  end
80
80
 
81
81
  args.each do |name|
@@ -84,7 +84,7 @@ module MongoDoc
84
84
  define_method("#{name}") do
85
85
  association = instance_variable_get("@#{name}")
86
86
  unless association
87
- association = Associations::CollectionProxy.new(:root => _root || self, :parent => self, :assoc_name => name, :assoc_class => assoc_class || self.class.type_name_with_module(name.to_s.classify).constantize)
87
+ association = Associations::CollectionProxy.new(:root => _root || self, :parent => self, :assoc_name => name, :assoc_class => assoc_class || self.class.class_from_name(name))
88
88
  instance_variable_set("@#{name}", association)
89
89
  end
90
90
  association
@@ -105,7 +105,7 @@ module MongoDoc
105
105
  def has_hash(*args)
106
106
  options = args.extract_options!
107
107
  assoc_class = if class_name = options.delete(:class_name)
108
- type_name_with_module(class_name).constantize
108
+ self.class_from_name(class_name)
109
109
  end
110
110
 
111
111
  args.each do |name|
@@ -114,7 +114,7 @@ module MongoDoc
114
114
  define_method("#{name}") do
115
115
  association = instance_variable_get("@#{name}")
116
116
  unless association
117
- association = Associations::HashProxy.new(:root => _root || self, :parent => self, :assoc_name => name, :assoc_class => assoc_class || self.class.type_name_with_module(name.to_s.classify).constantize)
117
+ association = Associations::HashProxy.new(:root => _root || self, :parent => self, :assoc_name => name, :assoc_class => assoc_class || self.class.class_from_name(name))
118
118
  instance_variable_set("@#{name}", association)
119
119
  end
120
120
  association
@@ -133,7 +133,7 @@ module MongoDoc
133
133
  end
134
134
 
135
135
  def type_name_with_module(type_name)
136
- (/^::/ =~ type_name) ? type_name : "#{parents}::#{type_name}"
136
+ (/^::/ =~ type_name) ? type_name : "#{parent}::#{type_name}"
137
137
  end
138
138
  end
139
139
  end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ require "mongoid/contexts/paging"
3
+ require "mongodoc/contexts/enumerable"
4
+ require "mongodoc/contexts/mongo"
5
+
6
+ module Mongoid
7
+ module Contexts
8
+ # Determines the context to be used for this criteria. If the class is an
9
+ # embedded document, then the context will be the array in the has_many
10
+ # association it is in. If the class is a root, then the database itself
11
+ # will be the context.
12
+ #
13
+ # Example:
14
+ #
15
+ # <tt>Contexts.context_for(criteria)</tt>
16
+ def self.context_for(criteria)
17
+ if criteria.klass.respond_to?(:collection)
18
+ return MongoDoc::Contexts::Mongo.new(criteria)
19
+ end
20
+ return MongoDoc::Contexts::Enumerable.new(criteria)
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,132 @@
1
+ # encoding: utf-8
2
+ module MongoDoc #:nodoc:
3
+ module Contexts #:nodoc:
4
+ class Enumerable
5
+ include Mongoid::Contexts::Paging
6
+ attr_reader :criteria
7
+
8
+ delegate :first, :last, :to => :execute
9
+ delegate :documents, :options, :selector, :to => :criteria
10
+
11
+ # Return aggregation counts of the grouped documents. This will count by
12
+ # the first field provided in the fields array.
13
+ #
14
+ # Returns:
15
+ #
16
+ # A +Hash+ with field values as keys, count as values
17
+ def aggregate
18
+ counts = {}
19
+ group.each_pair { |key, value| counts[key] = value.size }
20
+ counts
21
+ end
22
+
23
+ # Gets the number of documents in the array. Delegates to size.
24
+ def count
25
+ @count ||= documents.size
26
+ end
27
+
28
+ # Groups the documents by the first field supplied in the field options.
29
+ #
30
+ # Returns:
31
+ #
32
+ # A +Hash+ with field values as keys, arrays of documents as values.
33
+ def group
34
+ field = options[:fields].first
35
+ documents.group_by { |doc| doc.send(field) }
36
+ end
37
+
38
+ # Enumerable implementation of execute. Returns matching documents for
39
+ # the selector, and adds options if supplied.
40
+ #
41
+ # Returns:
42
+ #
43
+ # An +Array+ of documents that matched the selector.
44
+ def execute(paginating = false)
45
+ limit(documents.select { |document| document.matches?(selector) })
46
+ end
47
+
48
+ # Return documents based on an id search. Will handle if a single id has
49
+ # been passed or mulitple ids.
50
+ #
51
+ # Example:
52
+ #
53
+ # context.id_criteria([1, 2, 3])
54
+ #
55
+ # Returns:
56
+ #
57
+ # The single or multiple documents.
58
+ def id_criteria(params)
59
+ criteria.id(params)
60
+ result = params.is_a?(String) ? one : criteria.entries
61
+ return result
62
+ end
63
+
64
+ # Create the new enumerable context. This will need the selector and
65
+ # options from a +Criteria+ and a documents array that is the underlying
66
+ # array of embedded documents from a has many association.
67
+ #
68
+ # Example:
69
+ #
70
+ # <tt>MongoDoc::Contexts::Enumerable.new(criteria)</tt>
71
+ def initialize(criteria)
72
+ @criteria = criteria
73
+ end
74
+
75
+ # Get the largest value for the field in all the documents.
76
+ #
77
+ # Returns:
78
+ #
79
+ # The numerical largest value.
80
+ def max(field)
81
+ determine(field, :>=)
82
+ end
83
+
84
+ # Get the smallest value for the field in all the documents.
85
+ #
86
+ # Returns:
87
+ #
88
+ # The numerical smallest value.
89
+ def min(field)
90
+ determine(field, :<=)
91
+ end
92
+
93
+ # Get one document.
94
+ #
95
+ # Returns:
96
+ #
97
+ # The first document in the +Array+
98
+ alias :one :first
99
+
100
+ # Get the sum of the field values for all the documents.
101
+ #
102
+ # Returns:
103
+ #
104
+ # The numerical sum of all the document field values.
105
+ def sum(field)
106
+ sum = documents.inject(nil) do |memo, doc|
107
+ value = doc.send(field)
108
+ memo ? memo += value : value
109
+ end
110
+ end
111
+
112
+ protected
113
+ # If the field exists, perform the comparison and set if true.
114
+ def determine(field, operator)
115
+ matching = documents.inject(nil) do |memo, doc|
116
+ value = doc.send(field)
117
+ (memo && memo.send(operator, value)) ? memo : value
118
+ end
119
+ end
120
+
121
+ # Limits the result set if skip and limit options.
122
+ def limit(documents)
123
+ skip, limit = options[:skip], options[:limit]
124
+ if skip && limit
125
+ return documents.slice(skip, limit)
126
+ end
127
+ documents
128
+ end
129
+ end
130
+ end
131
+ end
132
+