mongo_doc 0.3.0

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 (122) hide show
  1. data/.document +5 -0
  2. data/.gitignore +7 -0
  3. data/LICENSE +20 -0
  4. data/README.textile +174 -0
  5. data/Rakefile +135 -0
  6. data/TODO +31 -0
  7. data/VERSION +1 -0
  8. data/data/.gitignore +2 -0
  9. data/examples/simple_document.rb +35 -0
  10. data/examples/simple_object.rb +30 -0
  11. data/features/finders.feature +76 -0
  12. data/features/mongodb.yml +7 -0
  13. data/features/mongodoc_base.feature +128 -0
  14. data/features/new_record.feature +36 -0
  15. data/features/partial_updates.feature +105 -0
  16. data/features/removing_documents.feature +68 -0
  17. data/features/saving_an_object.feature +15 -0
  18. data/features/scopes.feature +66 -0
  19. data/features/step_definitions/collection_steps.rb +14 -0
  20. data/features/step_definitions/document_steps.rb +149 -0
  21. data/features/step_definitions/documents.rb +30 -0
  22. data/features/step_definitions/finder_steps.rb +15 -0
  23. data/features/step_definitions/json_steps.rb +9 -0
  24. data/features/step_definitions/object_steps.rb +50 -0
  25. data/features/step_definitions/objects.rb +24 -0
  26. data/features/step_definitions/partial_update_steps.rb +32 -0
  27. data/features/step_definitions/query_steps.rb +54 -0
  28. data/features/step_definitions/removing_documents_steps.rb +14 -0
  29. data/features/step_definitions/scope_steps.rb +18 -0
  30. data/features/step_definitions/util_steps.rb +7 -0
  31. data/features/support/support.rb +10 -0
  32. data/features/using_criteria.feature +128 -0
  33. data/lib/mongo_doc/associations/collection_proxy.rb +105 -0
  34. data/lib/mongo_doc/associations/document_proxy.rb +56 -0
  35. data/lib/mongo_doc/associations/hash_proxy.rb +98 -0
  36. data/lib/mongo_doc/associations/proxy_base.rb +53 -0
  37. data/lib/mongo_doc/attributes.rb +140 -0
  38. data/lib/mongo_doc/bson.rb +45 -0
  39. data/lib/mongo_doc/collection.rb +55 -0
  40. data/lib/mongo_doc/connection.rb +88 -0
  41. data/lib/mongo_doc/contexts/enumerable.rb +128 -0
  42. data/lib/mongo_doc/contexts/ids.rb +41 -0
  43. data/lib/mongo_doc/contexts/mongo.rb +232 -0
  44. data/lib/mongo_doc/contexts.rb +25 -0
  45. data/lib/mongo_doc/criteria.rb +38 -0
  46. data/lib/mongo_doc/cursor.rb +32 -0
  47. data/lib/mongo_doc/document.rb +216 -0
  48. data/lib/mongo_doc/ext/array.rb +5 -0
  49. data/lib/mongo_doc/ext/binary.rb +7 -0
  50. data/lib/mongo_doc/ext/boolean_class.rb +11 -0
  51. data/lib/mongo_doc/ext/date.rb +16 -0
  52. data/lib/mongo_doc/ext/date_time.rb +13 -0
  53. data/lib/mongo_doc/ext/dbref.rb +7 -0
  54. data/lib/mongo_doc/ext/hash.rb +7 -0
  55. data/lib/mongo_doc/ext/nil_class.rb +5 -0
  56. data/lib/mongo_doc/ext/numeric.rb +17 -0
  57. data/lib/mongo_doc/ext/object.rb +17 -0
  58. data/lib/mongo_doc/ext/object_id.rb +7 -0
  59. data/lib/mongo_doc/ext/regexp.rb +5 -0
  60. data/lib/mongo_doc/ext/string.rb +5 -0
  61. data/lib/mongo_doc/ext/symbol.rb +5 -0
  62. data/lib/mongo_doc/ext/time.rb +5 -0
  63. data/lib/mongo_doc/finders.rb +49 -0
  64. data/lib/mongo_doc/matchers.rb +35 -0
  65. data/lib/mongo_doc/query.rb +7 -0
  66. data/lib/mongo_doc/scope.rb +64 -0
  67. data/lib/mongo_doc/validations/macros.rb +11 -0
  68. data/lib/mongo_doc/validations/validates_embedded.rb +13 -0
  69. data/lib/mongo_doc.rb +19 -0
  70. data/lib/mongoid/contexts/paging.rb +42 -0
  71. data/lib/mongoid/criteria.rb +247 -0
  72. data/lib/mongoid/criterion/complex.rb +21 -0
  73. data/lib/mongoid/criterion/exclusion.rb +65 -0
  74. data/lib/mongoid/criterion/inclusion.rb +92 -0
  75. data/lib/mongoid/criterion/optional.rb +136 -0
  76. data/lib/mongoid/extensions/hash/criteria_helpers.rb +20 -0
  77. data/lib/mongoid/extensions/symbol/inflections.rb +36 -0
  78. data/lib/mongoid/matchers/all.rb +11 -0
  79. data/lib/mongoid/matchers/default.rb +26 -0
  80. data/lib/mongoid/matchers/exists.rb +13 -0
  81. data/lib/mongoid/matchers/gt.rb +11 -0
  82. data/lib/mongoid/matchers/gte.rb +11 -0
  83. data/lib/mongoid/matchers/in.rb +11 -0
  84. data/lib/mongoid/matchers/lt.rb +11 -0
  85. data/lib/mongoid/matchers/lte.rb +11 -0
  86. data/lib/mongoid/matchers/ne.rb +11 -0
  87. data/lib/mongoid/matchers/nin.rb +11 -0
  88. data/lib/mongoid/matchers/size.rb +11 -0
  89. data/mongo_doc.gemspec +205 -0
  90. data/mongod.example.yml +2 -0
  91. data/mongodb.example.yml +14 -0
  92. data/perf/mongo_doc_runner.rb +90 -0
  93. data/perf/ruby_driver_runner.rb +64 -0
  94. data/script/console +8 -0
  95. data/spec/associations/collection_proxy_spec.rb +200 -0
  96. data/spec/associations/document_proxy_spec.rb +42 -0
  97. data/spec/associations/hash_proxy_spec.rb +163 -0
  98. data/spec/attributes_spec.rb +273 -0
  99. data/spec/bson_matchers.rb +54 -0
  100. data/spec/bson_spec.rb +196 -0
  101. data/spec/collection_spec.rb +161 -0
  102. data/spec/connection_spec.rb +147 -0
  103. data/spec/contexts/enumerable_spec.rb +274 -0
  104. data/spec/contexts/ids_spec.rb +49 -0
  105. data/spec/contexts/mongo_spec.rb +198 -0
  106. data/spec/contexts_spec.rb +28 -0
  107. data/spec/criteria_spec.rb +33 -0
  108. data/spec/cursor_spec.rb +91 -0
  109. data/spec/document_ext.rb +9 -0
  110. data/spec/document_spec.rb +664 -0
  111. data/spec/embedded_save_spec.rb +109 -0
  112. data/spec/finders_spec.rb +73 -0
  113. data/spec/hash_matchers.rb +27 -0
  114. data/spec/matchers_spec.rb +342 -0
  115. data/spec/mongodb.yml +6 -0
  116. data/spec/mongodb_pairs.yml +8 -0
  117. data/spec/new_record_spec.rb +128 -0
  118. data/spec/query_spec.rb +12 -0
  119. data/spec/scope_spec.rb +79 -0
  120. data/spec/spec.opts +2 -0
  121. data/spec/spec_helper.rb +13 -0
  122. metadata +290 -0
@@ -0,0 +1,247 @@
1
+ # encoding: utf-8
2
+ require "mongoid/criterion/complex"
3
+ require "mongoid/criterion/exclusion"
4
+ require "mongoid/criterion/inclusion"
5
+ require "mongoid/criterion/optional"
6
+
7
+ module Mongoid #:nodoc:
8
+ # The +Criteria+ class is the core object needed in Mongoid to retrieve
9
+ # objects from the database. It is a DSL that essentially sets up the
10
+ # selector and options arguments that get passed on to a <tt>Mongo::Collection</tt>
11
+ # in the Ruby driver. Each method on the +Criteria+ returns self to they
12
+ # can be chained in order to create a readable criterion to be executed
13
+ # against the database.
14
+ #
15
+ # Example setup:
16
+ #
17
+ # <tt>criteria = Criteria.new</tt>
18
+ #
19
+ # <tt>criteria.only(:field).where(:field => "value").skip(20).limit(20)</tt>
20
+ #
21
+ # <tt>criteria.execute</tt>
22
+ class Criteria
23
+ include Criterion::Exclusion
24
+ include Criterion::Inclusion
25
+ include Criterion::Optional
26
+ include Enumerable
27
+
28
+ attr_reader :collection, :ids, :klass, :options, :selector
29
+
30
+ attr_accessor :documents
31
+
32
+ delegate \
33
+ :aggregate,
34
+ :count,
35
+ :execute,
36
+ :first,
37
+ :group,
38
+ :id_criteria,
39
+ :last,
40
+ :max,
41
+ :min,
42
+ :one,
43
+ :page,
44
+ :paginate,
45
+ :per_page,
46
+ :sum, :to => :context
47
+
48
+ # Concatinate the criteria with another enumerable. If the other is a
49
+ # +Criteria+ then it needs to get the collection from it.
50
+ def +(other)
51
+ entries + comparable(other)
52
+ end
53
+
54
+ # Returns the difference between the criteria and another enumerable. If
55
+ # the other is a +Criteria+ then it needs to get the collection from it.
56
+ def -(other)
57
+ entries - comparable(other)
58
+ end
59
+
60
+ # Returns true if the supplied +Enumerable+ or +Criteria+ is equal to the results
61
+ # of this +Criteria+ or the criteria itself.
62
+ #
63
+ # This will force a database load when called if an enumerable is passed.
64
+ #
65
+ # Options:
66
+ #
67
+ # other: The other +Enumerable+ or +Criteria+ to compare to.
68
+ def ==(other)
69
+ case other
70
+ when Criteria
71
+ self.selector == other.selector && self.options == other.options
72
+ when Enumerable
73
+ return (execute.entries == other)
74
+ else
75
+ return false
76
+ end
77
+ end
78
+
79
+ # Returns true if the criteria is empty.
80
+ #
81
+ # Example:
82
+ #
83
+ # <tt>criteria.blank?</tt>
84
+ def blank?
85
+ count < 1
86
+ end
87
+
88
+ alias :empty? :blank?
89
+
90
+ # Return or create the context in which this criteria should be executed.
91
+ #
92
+ # This will return an Enumerable context if the class is embedded,
93
+ # otherwise it will return a Mongo context for root classes.
94
+ def context
95
+ @context ||= Contexts.context_for(self)
96
+ end
97
+
98
+ # Iterate over each +Document+ in the results. This can take an optional
99
+ # block to pass to each argument in the results.
100
+ #
101
+ # Example:
102
+ #
103
+ # <tt>criteria.each { |doc| p doc }</tt>
104
+ def each(&block)
105
+ context.iterate(&block)
106
+ self
107
+ end
108
+
109
+ # Merges the supplied argument hash into a single criteria
110
+ #
111
+ # Options:
112
+ #
113
+ # criteria_conditions: Hash of criteria keys, and parameter values
114
+ #
115
+ # Example:
116
+ #
117
+ # <tt>criteria.fuse(:where => { :field => "value"}, :limit => 20)</tt>
118
+ #
119
+ # Returns <tt>self</tt>
120
+ def fuse(criteria_conditions = {})
121
+ criteria_conditions.inject(self) do |criteria, (key, value)|
122
+ criteria.send(key, value)
123
+ end
124
+ end
125
+
126
+ # Create the new +Criteria+ object. This will initialize the selector
127
+ # and options hashes, as well as the type of criteria.
128
+ #
129
+ # Options:
130
+ #
131
+ # type: One of :all, :first:, or :last
132
+ # klass: The class to execute on.
133
+ def initialize(klass)
134
+ @selector, @options, @klass, @documents = {}, {}, klass, []
135
+ end
136
+
137
+ # Merges another object into this +Criteria+. The other object may be a
138
+ # +Criteria+ or a +Hash+. This is used to combine multiple scopes together,
139
+ # where a chained scope situation may be desired.
140
+ #
141
+ # Options:
142
+ #
143
+ # other: The +Criteria+ or +Hash+ to merge with.
144
+ #
145
+ # Example:
146
+ #
147
+ # <tt>criteria.merge({ :conditions => { :title => "Sir" } })</tt>
148
+ def merge(other)
149
+ @selector.update(other.selector)
150
+ @options.update(other.options)
151
+ @documents = other.documents
152
+ end
153
+
154
+ # Used for chaining +Criteria+ scopes together in the for of class methods
155
+ # on the +Document+ the criteria is for.
156
+ #
157
+ # Options:
158
+ #
159
+ # name: The name of the class method on the +Document+ to chain.
160
+ # args: The arguments passed to the method.
161
+ #
162
+ # Returns: <tt>Criteria</tt>
163
+ def method_missing(name, *args)
164
+ if @klass.respond_to?(name)
165
+ new_scope = @klass.send(name, *args)
166
+ new_scope.merge(self)
167
+ return new_scope
168
+ else
169
+ return entries.send(name, *args)
170
+ end
171
+ end
172
+
173
+ alias :to_ary :to_a
174
+
175
+ # Returns the selector and options as a +Hash+ that would be passed to a
176
+ # scope for use with named scopes.
177
+ def scoped
178
+ { :where => @selector }.merge(@options)
179
+ end
180
+
181
+ # Translate the supplied arguments into a +Criteria+ object.
182
+ #
183
+ # If the passed in args is a single +String+, then it will
184
+ # construct an id +Criteria+ from it.
185
+ #
186
+ # If the passed in args are a type and a hash, then it will construct
187
+ # the +Criteria+ with the proper selector, options, and type.
188
+ #
189
+ # Options:
190
+ #
191
+ # args: either a +String+ or a +Symbol+, +Hash combination.
192
+ #
193
+ # Example:
194
+ #
195
+ # <tt>Criteria.translate(Person, "4ab2bc4b8ad548971900005c")</tt>
196
+ # <tt>Criteria.translate(Person, :conditions => { :field => "value"}, :limit => 20)</tt>
197
+ def self.translate(*args)
198
+ klass = args[0]
199
+ params = args[1] || {}
200
+ unless params.is_a?(Hash)
201
+ return new(klass).id_criteria(params)
202
+ end
203
+ conditions = params.delete(:conditions) || {}
204
+ if conditions.include?(:id)
205
+ conditions[:_id] = conditions[:id]
206
+ conditions.delete(:id)
207
+ end
208
+ return new(klass).where(conditions).extras(params)
209
+ end
210
+
211
+ protected
212
+
213
+ # Filters the unused options out of the options +Hash+. Currently this
214
+ # takes into account the "page" and "per_page" options that would be passed
215
+ # in if using will_paginate.
216
+ #
217
+ # Example:
218
+ #
219
+ # Given a criteria with a selector of { :page => 1, :per_page => 40 }
220
+ #
221
+ # <tt>criteria.filter_options</tt> # selector: { :skip => 0, :limit => 40 }
222
+ def filter_options
223
+ page_num = @options.delete(:page)
224
+ per_page_num = @options.delete(:per_page)
225
+ if (page_num || per_page_num)
226
+ @options[:limit] = limits = (per_page_num || 20).to_i
227
+ @options[:skip] = (page_num || 1).to_i * limits - limits
228
+ end
229
+ end
230
+
231
+ # Return the entries of the other criteria or the object. Used for
232
+ # comparing criteria or an enumerable.
233
+ def comparable(other)
234
+ other.is_a?(Criteria) ? other.entries : other
235
+ end
236
+
237
+ # Update the selector setting the operator on the value for each key in the
238
+ # supplied attributes +Hash+.
239
+ #
240
+ # Example:
241
+ #
242
+ # <tt>criteria.update_selector({ :field => "value" }, "$in")</tt>
243
+ def update_selector(attributes, operator)
244
+ attributes.each { |key, value| @selector[key] = { operator => value } }; self
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Criterion #:nodoc:
4
+ # Complex criterion are used when performing operations on symbols to get
5
+ # get a shorthand syntax for where clauses.
6
+ #
7
+ # Example:
8
+ #
9
+ # <tt>{ :field => { "$lt" => "value" } }</tt>
10
+ # becomes:
11
+ # <tt> { :field.lt => "value }</tt>
12
+ class Complex
13
+ attr_accessor :key, :operator
14
+
15
+ # Create the new complex criterion.
16
+ def initialize(opts = {})
17
+ @key, @operator = opts[:key], opts[:operator]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Criterion #:nodoc:
4
+ module Exclusion
5
+ # Adds a criterion to the +Criteria+ that specifies values that are not allowed
6
+ # to match any document in the database. The MongoDB conditional operator that
7
+ # will be used is "$ne".
8
+ #
9
+ # Options:
10
+ #
11
+ # attributes: A +Hash+ where the key is the field name and the value is a
12
+ # value that must not be equal to the corresponding field value in the database.
13
+ #
14
+ # Example:
15
+ #
16
+ # <tt>criteria.excludes(:field => "value1")</tt>
17
+ #
18
+ # <tt>criteria.excludes(:field1 => "value1", :field2 => "value1")</tt>
19
+ #
20
+ # Returns: <tt>self</tt>
21
+ def excludes(attributes = {})
22
+ mongo_id = attributes.delete(:id)
23
+ attributes = attributes.merge(:_id => mongo_id) if mongo_id
24
+ update_selector(attributes, "$ne")
25
+ end
26
+
27
+ # Adds a criterion to the +Criteria+ that specifies values where none
28
+ # should match in order to return results. This is similar to an SQL "NOT IN"
29
+ # clause. The MongoDB conditional operator that will be used is "$nin".
30
+ #
31
+ # Options:
32
+ #
33
+ # exclusions: A +Hash+ where the key is the field name and the value is an
34
+ # +Array+ of values that none can match.
35
+ #
36
+ # Example:
37
+ #
38
+ # <tt>criteria.not_in(:field => ["value1", "value2"])</tt>
39
+ #
40
+ # <tt>criteria.not_in(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
41
+ #
42
+ # Returns: <tt>self</tt>
43
+ def not_in(exclusions)
44
+ exclusions.each { |key, value| @selector[key] = { "$nin" => value } }; self
45
+ end
46
+
47
+ # Adds a criterion to the +Criteria+ that specifies the fields that will
48
+ # get returned from the Document. Used mainly for list views that do not
49
+ # require all fields to be present. This is similar to SQL "SELECT" values.
50
+ #
51
+ # Options:
52
+ #
53
+ # args: A list of field names to retrict the returned fields to.
54
+ #
55
+ # Example:
56
+ #
57
+ # <tt>criteria.only(:field1, :field2, :field3)</tt>
58
+ #
59
+ # Returns: <tt>self</tt>
60
+ def only(*args)
61
+ @options[:fields] = args.flatten if args.any?; self
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,92 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Criterion #:nodoc:
4
+ module Inclusion
5
+ # Adds a criterion to the +Criteria+ that specifies values that must all
6
+ # be matched in order to return results. Similar to an "in" clause but the
7
+ # underlying conditional logic is an "AND" and not an "OR". The MongoDB
8
+ # conditional operator that will be used is "$all".
9
+ #
10
+ # Options:
11
+ #
12
+ # attributes: A +Hash+ where the key is the field name and the value is an
13
+ # +Array+ of values that must all match.
14
+ #
15
+ # Example:
16
+ #
17
+ # <tt>criteria.all(:field => ["value1", "value2"])</tt>
18
+ #
19
+ # <tt>criteria.all(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
20
+ #
21
+ # Returns: <tt>self</tt>
22
+ def all(attributes = {})
23
+ update_selector(attributes, "$all")
24
+ end
25
+
26
+ # Adds a criterion to the +Criteria+ that specifies values that must
27
+ # be matched in order to return results. This is similar to a SQL "WHERE"
28
+ # clause. This is the actual selector that will be provided to MongoDB,
29
+ # similar to the Javascript object that is used when performing a find()
30
+ # in the MongoDB console.
31
+ #
32
+ # Options:
33
+ #
34
+ # selectior: A +Hash+ that must match the attributes of the +Document+.
35
+ #
36
+ # Example:
37
+ #
38
+ # <tt>criteria.and(:field1 => "value1", :field2 => 15)</tt>
39
+ #
40
+ # Returns: <tt>self</tt>
41
+ def and(selector = nil)
42
+ where(selector)
43
+ end
44
+
45
+ # Adds a criterion to the +Criteria+ that specifies values where any can
46
+ # be matched in order to return results. This is similar to an SQL "IN"
47
+ # clause. The MongoDB conditional operator that will be used is "$in".
48
+ #
49
+ # Options:
50
+ #
51
+ # attributes: A +Hash+ where the key is the field name and the value is an
52
+ # +Array+ of values that any can match.
53
+ #
54
+ # Example:
55
+ #
56
+ # <tt>criteria.in(:field => ["value1", "value2"])</tt>
57
+ #
58
+ # <tt>criteria.in(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
59
+ #
60
+ # Returns: <tt>self</tt>
61
+ def in(attributes = {})
62
+ update_selector(attributes, "$in")
63
+ end
64
+ alias any_in in
65
+
66
+ # Adds a criterion to the +Criteria+ that specifies values that must
67
+ # be matched in order to return results. This is similar to a SQL "WHERE"
68
+ # clause. This is the actual selector that will be provided to MongoDB,
69
+ # similar to the Javascript object that is used when performing a find()
70
+ # in the MongoDB console.
71
+ #
72
+ # Options:
73
+ #
74
+ # selectior: A +Hash+ that must match the attributes of the +Document+.
75
+ #
76
+ # Example:
77
+ #
78
+ # <tt>criteria.where(:field1 => "value1", :field2 => 15)</tt>
79
+ #
80
+ # Returns: <tt>self</tt>
81
+ def where(selector = nil)
82
+ case selector
83
+ when String
84
+ @selector.update("$where" => selector)
85
+ else
86
+ @selector.update(selector ? selector.expand_complex_criteria : {})
87
+ end
88
+ self
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,136 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Criterion #:nodoc:
4
+ module Optional
5
+ # Tells the criteria that the cursor that gets returned needs to be
6
+ # cached. This is so multiple iterations don't hit the database multiple
7
+ # times, however this is not advisable when working with large data sets
8
+ # as the entire results will get stored in memory.
9
+ #
10
+ # Example:
11
+ #
12
+ # <tt>criteria.cache</tt>
13
+ def cache
14
+ @options.merge!(:cache => true); self
15
+ end
16
+
17
+ # Will return true if the cache option has been set.
18
+ #
19
+ # Example:
20
+ #
21
+ # <tt>criteria.cached?</tt>
22
+ def cached?
23
+ @options[:cache] == true
24
+ end
25
+
26
+ # Flags the criteria to execute against a read-only slave in the pool
27
+ # instead of master.
28
+ #
29
+ # Example:
30
+ #
31
+ # <tt>criteria.enslave</tt>
32
+ def enslave
33
+ @options.merge!(:enslave => true); self
34
+ end
35
+
36
+ # Will return true if the criteria is enslaved.
37
+ #
38
+ # Example:
39
+ #
40
+ # <tt>criteria.enslaved?</tt>
41
+ def enslaved?
42
+ @options[:enslave] == true
43
+ end
44
+
45
+ # Adds a criterion to the +Criteria+ that specifies additional options
46
+ # to be passed to the Ruby driver, in the exact format for the driver.
47
+ #
48
+ # Options:
49
+ #
50
+ # extras: A +Hash+ that gets set to the driver options.
51
+ #
52
+ # Example:
53
+ #
54
+ # <tt>criteria.extras(:limit => 20, :skip => 40)</tt>
55
+ #
56
+ # Returns: <tt>self</tt>
57
+ def extras(extras)
58
+ @options.merge!(extras); filter_options; self
59
+ end
60
+
61
+ # Adds a criterion to the +Criteria+ that specifies an id that must be matched.
62
+ #
63
+ # Options:
64
+ #
65
+ # object_id: A +String+ representation of a <tt>Mongo::ObjectID</tt>
66
+ #
67
+ # Example:
68
+ #
69
+ # <tt>criteria.id("4ab2bc4b8ad548971900005c")</tt>
70
+ #
71
+ # Returns: <tt>self</tt>
72
+ def id(*args)
73
+ (args.flatten.size > 1) ? self.in(:_id => args.flatten) : (@selector[:_id] = args.first)
74
+ self
75
+ end
76
+
77
+ # Adds a criterion to the +Criteria+ that specifies the maximum number of
78
+ # results to return. This is mostly used in conjunction with <tt>skip()</tt>
79
+ # to handle paginated results.
80
+ #
81
+ # Options:
82
+ #
83
+ # value: An +Integer+ specifying the max number of results. Defaults to 20.
84
+ #
85
+ # Example:
86
+ #
87
+ # <tt>criteria.limit(100)</tt>
88
+ #
89
+ # Returns: <tt>self</tt>
90
+ def limit(value = 20)
91
+ @options[:limit] = value; self
92
+ end
93
+
94
+ # Returns the offset option. If a per_page option is in the list then it
95
+ # will replace it with a skip parameter and return the same value. Defaults
96
+ # to 20 if nothing was provided.
97
+ def offset(*args)
98
+ args.size > 0 ? skip(args.first) : @options[:skip]
99
+ end
100
+
101
+ # Adds a criterion to the +Criteria+ that specifies the sort order of
102
+ # the returned documents in the database. Similar to a SQL "ORDER BY".
103
+ #
104
+ # Options:
105
+ #
106
+ # params: An +Array+ of [field, direction] sorting pairs.
107
+ #
108
+ # Example:
109
+ #
110
+ # <tt>criteria.order_by([[:field1, :asc], [:field2, :desc]])</tt>
111
+ #
112
+ # Returns: <tt>self</tt>
113
+ def order_by(params = [])
114
+ @options[:sort] = params; self
115
+ end
116
+
117
+ # Adds a criterion to the +Criteria+ that specifies how many results to skip
118
+ # when returning Documents. This is mostly used in conjunction with
119
+ # <tt>limit()</tt> to handle paginated results, and is similar to the
120
+ # traditional "offset" parameter.
121
+ #
122
+ # Options:
123
+ #
124
+ # value: An +Integer+ specifying the number of results to skip. Defaults to 0.
125
+ #
126
+ # Example:
127
+ #
128
+ # <tt>criteria.skip(20)</tt>
129
+ #
130
+ # Returns: <tt>self</tt>
131
+ def skip(value = 0)
132
+ @options[:skip] = value; self
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Extensions #:nodoc:
4
+ module Hash #:nodoc:
5
+ module CriteriaHelpers #:nodoc:
6
+ def expand_complex_criteria
7
+ hsh = {}
8
+ self.each_pair do |k,v|
9
+ if k.class == Mongoid::Criterion::Complex
10
+ hsh[k.key] = {"$#{k.operator}" => v}
11
+ else
12
+ hsh[k] = v
13
+ end
14
+ end
15
+ hsh
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Extensions #:nodoc:
4
+ module Symbol #:nodoc:
5
+ module Inflections #:nodoc:
6
+
7
+ REVERSALS = {
8
+ :asc => :desc,
9
+ :ascending => :descending,
10
+ :desc => :asc,
11
+ :descending => :ascending
12
+ }
13
+
14
+ def invert
15
+ REVERSALS[self]
16
+ end
17
+
18
+ def singular?
19
+ to_s.singular?
20
+ end
21
+
22
+ def plural?
23
+ to_s.plural?
24
+ end
25
+
26
+ ["gt", "lt", "gte", "lte", "ne", "in", "nin", "mod", "all", "size", "exists"].each do |oper|
27
+ class_eval <<-OPERATORS
28
+ def #{oper}
29
+ Criterion::Complex.new(:key => self, :operator => "#{oper}")
30
+ end
31
+ OPERATORS
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Matchers #:nodoc:
4
+ class All < Default
5
+ # Return true if the attribute and first value in the hash are equal.
6
+ def matches?(value)
7
+ @attribute == value.values.first
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Matchers #:nodoc:
4
+ class Default
5
+ # Creating a new matcher only requires the value.
6
+ def initialize(attribute)
7
+ @attribute = attribute
8
+ end
9
+ # Return true if the attribute and value are equal.
10
+ def matches?(value)
11
+ @attribute == value
12
+ end
13
+
14
+ protected
15
+ # Return the first value in the hash.
16
+ def first(value)
17
+ value.values.first
18
+ end
19
+
20
+ # If object exists then compare, else return false
21
+ def determine(value, operator)
22
+ @attribute ? @attribute.send(operator, first(value)) : false
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Matchers #:nodoc:
4
+ class Exists < Default
5
+ # Return true if the attribute exists and checking for existence or
6
+ # return true if the attribute does not exist and checking for
7
+ # non-existence.
8
+ def matches?(value)
9
+ @attribute.nil? != value.values.first
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Matchers #:nodoc:
4
+ class Gt < Default
5
+ # Return true if the attribute is greater than the value.
6
+ def matches?(value)
7
+ determine(value, :>)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Matchers #:nodoc:
4
+ class Gte < Default
5
+ # Return true if the attribute is greater than or equal to the value.
6
+ def matches?(value)
7
+ determine(value, :>=)
8
+ end
9
+ end
10
+ end
11
+ end