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
@@ -0,0 +1,215 @@
1
+ module MongoDoc
2
+ module Contexts
3
+ class Mongo
4
+ include Mongoid::Contexts::Paging
5
+ attr_reader :criteria
6
+
7
+ delegate :klass, :options, :selector, :to => :criteria
8
+ delegate :collection, :to => :klass
9
+
10
+ AGGREGATE_REDUCE = "function(obj, prev) { prev.count++; }"
11
+ # Aggregate the context. This will take the internally built selector and options
12
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
13
+ # collection itself will be retrieved from the class provided, and once the
14
+ # query has returned it will provided a grouping of keys with counts.
15
+ #
16
+ # Example:
17
+ #
18
+ # <tt>context.aggregate</tt>
19
+ #
20
+ # Returns:
21
+ #
22
+ # A +Hash+ with field values as keys, counts as values
23
+ def aggregate
24
+ collection.group(options[:fields], selector, { :count => 0 }, AGGREGATE_REDUCE, true)
25
+ end
26
+
27
+ # Get the count of matching documents in the database for the context.
28
+ #
29
+ # Example:
30
+ #
31
+ # <tt>context.count</tt>
32
+ #
33
+ # Returns:
34
+ #
35
+ # An +Integer+ count of documents.
36
+ def count
37
+ @count ||= collection.find(selector, options).count
38
+ end
39
+
40
+ # Execute the context. This will take the selector and options
41
+ # and pass them on to the Ruby driver's +find()+ method on the collection. The
42
+ # collection itself will be retrieved from the class provided, and once the
43
+ # query has returned new documents of the type of class provided will be instantiated.
44
+ #
45
+ # Example:
46
+ #
47
+ # <tt>mongo.execute</tt>
48
+ #
49
+ # Returns:
50
+ #
51
+ # An enumerable +Cursor+.
52
+ def execute(paginating = false)
53
+ cursor = collection.find(selector, options)
54
+ if cursor
55
+ @count = cursor.count if paginating
56
+ cursor
57
+ else
58
+ []
59
+ end
60
+ end
61
+
62
+ GROUP_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
63
+ # Groups the context. This will take the internally built selector and options
64
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
65
+ # collection itself will be retrieved from the class provided, and once the
66
+ # query has returned it will provided a grouping of keys with objects.
67
+ #
68
+ # Example:
69
+ #
70
+ # <tt>context.group</tt>
71
+ #
72
+ # Returns:
73
+ #
74
+ # A +Hash+ with field values as keys, arrays of documents as values.
75
+ def group
76
+ collection.group(
77
+ options[:fields],
78
+ selector,
79
+ { :group => [] },
80
+ GROUP_REDUCE,
81
+ true
82
+ ).collect {|docs| docs["group"] = MongoDoc::BSON.decode(docs["group"]); docs }
83
+ end
84
+
85
+ # Return documents based on an id search. Will handle if a single id has
86
+ # been passed or mulitple ids.
87
+ #
88
+ # Example:
89
+ #
90
+ # context.id_criteria([1, 2, 3])
91
+ #
92
+ # Returns:
93
+ #
94
+ # The single or multiple documents.
95
+ def id_criteria(params)
96
+ criteria.id(params)
97
+ params.is_a?(Array) ? criteria.entries : one
98
+ end
99
+
100
+ # Create the new mongo context. This will execute the queries given the
101
+ # selector and options against the database.
102
+ #
103
+ # Example:
104
+ #
105
+ # <tt>Mongoid::Contexts::Mongo.new(criteria)</tt>
106
+ def initialize(criteria)
107
+ @criteria = criteria
108
+ end
109
+
110
+ # Return the last result for the +Context+. Essentially does a find_one on
111
+ # the collection with the sorting reversed. If no sorting parameters have
112
+ # been provided it will default to ids.
113
+ #
114
+ # Example:
115
+ #
116
+ # <tt>context.last</tt>
117
+ #
118
+ # Returns:
119
+ #
120
+ # The last document in the collection.
121
+ def last
122
+ sorting = options[:sort] || [[:_id, :asc]]
123
+ options[:sort] = sorting.collect { |option| [ option[0], option[1].invert ] }
124
+ collection.find_one(selector, options)
125
+ end
126
+
127
+ MAX_REDUCE = "function(obj, prev) { if (prev.max == 'start') { prev.max = obj.[field]; } " +
128
+ "if (prev.max < obj.[field]) { prev.max = obj.[field]; } }"
129
+ # Return the max value for a field.
130
+ #
131
+ # This will take the internally built selector and options
132
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
133
+ # collection itself will be retrieved from the class provided, and once the
134
+ # query has returned it will provided a grouping of keys with sums.
135
+ #
136
+ # Example:
137
+ #
138
+ # <tt>context.max(:age)</tt>
139
+ #
140
+ # Returns:
141
+ #
142
+ # A numeric max value.
143
+ def max(field)
144
+ grouped(:max, field.to_s, MAX_REDUCE)
145
+ end
146
+
147
+ MIN_REDUCE = "function(obj, prev) { if (prev.min == 'start') { prev.min = obj.[field]; } " +
148
+ "if (prev.min > obj.[field]) { prev.min = obj.[field]; } }"
149
+ # Return the min value for a field.
150
+ #
151
+ # This will take the internally built selector and options
152
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
153
+ # collection itself will be retrieved from the class provided, and once the
154
+ # query has returned it will provided a grouping of keys with sums.
155
+ #
156
+ # Example:
157
+ #
158
+ # <tt>context.min(:age)</tt>
159
+ #
160
+ # Returns:
161
+ #
162
+ # A numeric minimum value.
163
+ def min(field)
164
+ grouped(:min, field.to_s, MIN_REDUCE)
165
+ end
166
+
167
+ # Return the first result for the +Context+.
168
+ #
169
+ # Example:
170
+ #
171
+ # <tt>context.one</tt>
172
+ #
173
+ # Return:
174
+ #
175
+ # The first document in the collection.
176
+ def one
177
+ collection.find_one(selector, options)
178
+ end
179
+
180
+ alias :first :one
181
+
182
+ SUM_REDUCE = "function(obj, prev) { if (prev.sum == 'start') { prev.sum = 0; } prev.sum += obj.[field]; }"
183
+ # Sum the context.
184
+ #
185
+ # This will take the internally built selector and options
186
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
187
+ # collection itself will be retrieved from the class provided, and once the
188
+ # query has returned it will provided a grouping of keys with sums.
189
+ #
190
+ # Example:
191
+ #
192
+ # <tt>context.sum(:age)</tt>
193
+ #
194
+ # Returns:
195
+ #
196
+ # A numeric value that is the sum.
197
+ def sum(field)
198
+ grouped(:sum, field.to_s, SUM_REDUCE)
199
+ end
200
+
201
+ # Common functionality for grouping operations. Currently used by min, max
202
+ # and sum. Will gsub the field name in the supplied reduce function.
203
+ def grouped(start, field, reduce)
204
+ result = collection.group(
205
+ nil,
206
+ selector,
207
+ { start => "start" },
208
+ reduce.gsub("[field]", field),
209
+ true
210
+ )
211
+ result.empty? ? nil : result.first[start.to_s]
212
+ end
213
+ end
214
+ end
215
+ end
@@ -1,481 +1,38 @@
1
- # This awesomeness is taken from Mongoid, Thanks Durran!
2
-
3
- module MongoDoc #:nodoc:
4
- # The +Criteria+ class is the core object needed in Mongoid to retrieve
5
- # objects from the database. It is a DSL that essentially sets up the
6
- # selector and options arguments that get passed on to a <tt>Mongo::Collection</tt>
7
- # in the Ruby driver. Each method on the +Criteria+ returns self to they
8
- # can be chained in order to create a readable criterion to be executed
9
- # against the database.
10
- #
11
- # Example setup:
12
- #
13
- # <tt>criteria = Criteria.new</tt>
14
- #
15
- # <tt>criteria.only(:field => "value").only(:field).skip(20).limit(20)</tt>
16
- #
17
- # <tt>criteria.execute</tt>
18
- class Criteria
19
- SORT_REVERSALS = {
20
- :asc => :desc,
21
- :ascending => :descending,
22
- :desc => :asc,
23
- :descending => :ascending
24
- }
25
-
26
- include Enumerable
27
-
28
- attr_reader :collection, :klass, :options, :selector
29
-
30
- # Create the new +Criteria+ object. This will initialize the selector
31
- # and options hashes, as well as the type of criteria.
32
- #
33
- # Options:
34
- #
35
- # klass: The class to execute on.
36
- def initialize(klass)
37
- @selector, @options, @klass = {}, {}, klass
38
- end
39
-
40
- # Returns true if the supplied +Enumerable+ or +Criteria+ is equal to the results
41
- # of this +Criteria+ or the criteria itself.
42
- #
43
- # This will force a database load when called if an enumerable is passed.
44
- #
45
- # Options:
46
- #
47
- # other: The other +Enumerable+ or +Criteria+ to compare to.
48
- def ==(other)
49
- case other
50
- when Criteria
51
- self.selector == other.selector && self.options == other.options
52
- when Enumerable
53
- @collection ||= execute
54
- return (collection == other)
55
- else
56
- return false
57
- end
58
- end
59
-
60
- AGGREGATE_REDUCE = "function(obj, prev) { prev.count++; }"
61
- # Aggregate the criteria. This will take the internally built selector and options
62
- # and pass them on to the Ruby driver's +group()+ method on the collection. The
63
- # collection itself will be retrieved from the class provided, and once the
64
- # query has returned it will provided a grouping of keys with counts.
65
- #
66
- # Example:
67
- #
68
- # <tt>criteria.only(:field1).where(:field1 => "Title").aggregate</tt>
69
- def aggregate
70
- klass.collection.group(options[:fields], selector, { :count => 0 }, AGGREGATE_REDUCE, true)
71
- end
72
-
73
- # Get all the matching documents in the database for the +Criteria+.
74
- #
75
- # Example:
76
- #
77
- # <tt>criteria.all</tt>
78
- #
79
- # Returns: <tt>Array</tt>
80
- def all
81
- collect
82
- end
83
-
84
- # Get the count of matching documents in the database for the +Criteria+.
85
- #
86
- # Example:
87
- #
88
- # <tt>criteria.count</tt>
89
- #
90
- # Returns: <tt>Integer</tt>
91
- def count
92
- @count ||= klass.collection.find(selector, options.dup).count
93
- end
94
-
95
- # Iterate over each +Document+ in the results and pass each document to the
96
- # block.
97
- #
98
- # Example:
99
- #
100
- # <tt>criteria.each { |doc| p doc }</tt>
101
- def each(&block)
102
- @collection ||= execute
103
- if block_given?
104
- container = []
105
- collection.each do |item|
106
- container << item
107
- yield item
108
- end
109
- @collection = container
110
- end
111
- self
112
- end
113
-
114
- GROUP_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
115
- # Groups the criteria. This will take the internally built selector and options
116
- # and pass them on to the Ruby driver's +group()+ method on the collection. The
117
- # collection itself will be retrieved from the class provided, and once the
118
- # query has returned it will provided a grouping of keys with objects.
119
- #
120
- # Example:
121
- #
122
- # <tt>criteria.only(:field1).where(:field1 => "Title").group</tt>
123
- def group
124
- klass.collection.group(
125
- options[:fields],
126
- selector,
127
- { :group => [] },
128
- GROUP_REDUCE,
129
- true
130
- ).collect {|docs| docs["group"] = MongoDoc::BSON.decode(docs["group"]); docs }
131
- end
132
-
133
- # Return the last result for the +Criteria+. Essentially does a find_one on
134
- # the collection with the sorting reversed. If no sorting parameters have
135
- # been provided it will default to ids.
136
- #
137
- # Example:
138
- #
139
- # <tt>Criteria.only(:name).where(:name = "Chrissy").last</tt>
140
- def last
141
- opts = options.dup
142
- sorting = opts[:sort]
143
- sorting = [[:_id, :asc]] unless sorting
144
- opts[:sort] = sorting.collect { |option| [ option.first, Criteria.invert(option.last) ] }
145
- klass.collection.find_one(selector, opts)
146
- end
147
-
148
- # Return the first result for the +Criteria+.
149
- #
150
- # Example:
151
- #
152
- # <tt>Criteria.only(:name).where(:name = "Chrissy").one</tt>
153
- def one
154
- klass.collection.find_one(selector, options.dup)
155
- end
156
- alias :first :one
157
-
158
- # Translate the supplied argument hash
159
- #
160
- # Options:
161
- #
162
- # criteria_conditions: Hash of criteria keys, and parameter values
163
- #
164
- # Example:
165
- #
166
- # <tt>criteria.criteria(:where => { :field => "value"}, :limit => 20)</tt>
167
- #
168
- # Returns <tt>self</tt>
169
- def criteria(criteria_conditions = {})
170
- criteria_conditions.each do |(key, value)|
171
- send(key, value)
172
- end
173
- self
174
- end
175
-
176
- # Adds a criterion to the +Criteria+ that specifies values that must all
177
- # be matched in order to return results. Similar to an "in" clause but the
178
- # underlying conditional logic is an "AND" and not an "OR". The MongoDB
179
- # conditional operator that will be used is "$all".
180
- #
181
- # Options:
182
- #
183
- # selections: A +Hash+ where the key is the field name and the value is an
184
- # +Array+ of values that must all match.
185
- #
186
- # Example:
187
- #
188
- # <tt>criteria.every(:field => ["value1", "value2"])</tt>
189
- #
190
- # <tt>criteria.every(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
191
- #
192
- # Returns: <tt>self</tt>
193
- def every(selections = {})
194
- selections.each { |key, value| selector[key] = { "$all" => value } }; self
195
- end
196
-
197
- # Adds a criterion to the +Criteria+ that specifies values that are not allowed
198
- # to match any document in the database. The MongoDB conditional operator that
199
- # will be used is "$ne".
200
- #
201
- # Options:
202
- #
203
- # excludes: A +Hash+ where the key is the field name and the value is a
204
- # value that must not be equal to the corresponding field value in the database.
205
- #
206
- # Example:
207
- #
208
- # <tt>criteria.excludes(:field => "value1")</tt>
209
- #
210
- # <tt>criteria.excludes(:field1 => "value1", :field2 => "value1")</tt>
211
- #
212
- # Returns: <tt>self</tt>
213
- def excludes(exclusions = {})
214
- exclusions.each { |key, value| selector[key] = { "$ne" => value } }; self
215
- end
216
-
217
- # Adds a criterion to the +Criteria+ that specifies additional options
218
- # to be passed to the Ruby driver, in the exact format for the driver.
219
- #
220
- # Options:
221
- #
222
- # extras: A +Hash+ that gets set to the driver options.
223
- #
224
- # Example:
225
- #
226
- # <tt>criteria.extras(:limit => 20, :skip => 40)</tt>
227
- #
228
- # Returns: <tt>self</tt>
229
- def extras(extras)
230
- options.merge!(extras)
231
- filter_options
232
- self
233
- end
234
-
235
- # Adds a criterion to the +Criteria+ that specifies an id that must be matched.
236
- #
237
- # Options:
238
- #
239
- # id_or_object_id: A +String+ representation of a <tt>Mongo::ObjectID</tt>
240
- #
241
- # Example:
242
- #
243
- # <tt>criteria.id("4ab2bc4b8ad548971900005c")</tt>
244
- #
245
- # Returns: <tt>self</tt>
246
- def id(id_or_object_id)
247
- if id_or_object_id.kind_of?(String)
248
- id_or_object_id = Mongo::ObjectID.from_string(id_or_object_id)
249
- end
250
- selector[:_id] = id_or_object_id; self
251
- end
252
-
253
- # Adds a criterion to the +Criteria+ that specifies values where any can
254
- # be matched in order to return results. This is similar to an SQL "IN"
255
- # clause. The MongoDB conditional operator that will be used is "$in".
256
- #
257
- # Options:
258
- #
259
- # inclusions: A +Hash+ where the key is the field name and the value is an
260
- # +Array+ of values that any can match.
261
- #
262
- # Example:
263
- #
264
- # <tt>criteria.in(:field => ["value1", "value2"])</tt>
265
- #
266
- # <tt>criteria.in(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
267
- #
268
- # Returns: <tt>self</tt>
269
- def in(inclusions = {})
270
- inclusions.each { |key, value| selector[key] = { "$in" => value } }; self
271
- end
272
-
273
- # Adds a criterion to the +Criteria+ that specifies the maximum number of
274
- # results to return. This is mostly used in conjunction with <tt>skip()</tt>
275
- # to handle paginated results.
276
- #
277
- # Options:
278
- #
279
- # value: An +Integer+ specifying the max number of results. Defaults to 20.
280
- #
281
- # Example:
282
- #
283
- # <tt>criteria.limit(100)</tt>
284
- #
285
- # Returns: <tt>self</tt>
286
- def limit(value = 20)
287
- options[:limit] = value; self
288
- end
289
-
290
- # Adds a criterion to the +Criteria+ that specifies values where none
291
- # should match in order to return results. This is similar to an SQL "NOT IN"
292
- # clause. The MongoDB conditional operator that will be used is "$nin".
293
- #
294
- # Options:
295
- #
296
- # exclusions: A +Hash+ where the key is the field name and the value is an
297
- # +Array+ of values that none can match.
298
- #
299
- # Example:
300
- #
301
- # <tt>criteria.not_in(:field => ["value1", "value2"])</tt>
302
- #
303
- # <tt>criteria.not_in(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
304
- #
305
- # Returns: <tt>self</tt>
306
- def not_in(exclusions)
307
- exclusions.each { |key, value| selector[key] = { "$nin" => value } }; self
308
- end
309
-
310
- # Returns the offset option. If a per_page option is in the list then it
311
- # will replace it with a skip parameter and return the same value. Defaults
312
- # to 20 if nothing was provided.
313
- def offset
314
- options[:skip]
315
- end
316
-
317
- # Adds a criterion to the +Criteria+ that specifies the sort order of
318
- # the returned documents in the database. Similar to a SQL "ORDER BY".
319
- #
320
- # Options:
321
- #
322
- # params: An +Array+ of [field, direction] sorting pairs.
323
- #
324
- # Example:
325
- #
326
- # <tt>criteria.order_by([[:field1, :asc], [:field2, :desc]])</tt>
327
- #
328
- # Returns: <tt>self</tt>
329
- def order_by(params = [])
330
- options[:sort] = params; self
331
- end
332
-
333
- # Either returns the page option and removes it from the options, or
334
- # returns a default value of 1.
335
- def page
336
- if options[:skip] && options[:limit]
337
- (options[:skip].to_i + options[:limit].to_i) / options[:limit].to_i
338
- else
339
- 1
340
- end
341
- end
342
-
343
- # Executes the +Criteria+ and paginates the results.
344
- #
345
- # Example:
346
- #
347
- # <tt>criteria.paginate</tt>
348
- def paginate
349
- @collection ||= execute
350
- WillPaginate::Collection.create(page, per_page, count) do |pager|
351
- pager.replace(collection.to_a)
352
- end
353
- end
354
-
355
- # Returns the number of results per page or the default of 20.
356
- def per_page
357
- (options[:limit] || 20).to_i
358
- end
359
-
360
- # Adds a criterion to the +Criteria+ that specifies the fields that will
361
- # get returned from the Document. Used mainly for list views that do not
362
- # require all fields to be present. This is similar to SQL "SELECT" values.
363
- #
364
- # Options:
365
- #
366
- # args: A list of field names to retrict the returned fields to.
367
- #
368
- # Example:
369
- #
370
- # <tt>criteria.only(:field1, :field2, :field3)</tt>
371
- #
372
- # Returns: <tt>self</tt>
373
- def only(*args)
374
- options[:fields] = args.flatten if args.any?; self
375
- end
376
-
377
- # Adds a criterion to the +Criteria+ that specifies how many results to skip
378
- # when returning Documents. This is mostly used in conjunction with
379
- # <tt>limit()</tt> to handle paginated results, and is similar to the
380
- # traditional "offset" parameter.
381
- #
382
- # Options:
383
- #
384
- # value: An +Integer+ specifying the number of results to skip. Defaults to 0.
385
- #
386
- # Example:
387
- #
388
- # <tt>criteria.skip(20)</tt>
389
- #
390
- # Returns: <tt>self</tt>
391
- def skip(value = 0)
392
- options[:skip] = value; self
393
- end
394
-
395
- # Adds a criterion to the +Criteria+ that specifies values that must
396
- # be matched in order to return results. This is similar to a SQL "WHERE"
397
- # clause. This is the actual selector that will be provided to MongoDB,
398
- # similar to the Javascript object that is used when performing a find()
399
- # in the MongoDB console.
400
- #
401
- # Options:
402
- #
403
- # selector_or_js: A +Hash+ that must match the attributes of the +Document+
404
- # or a +String+ of js code.
405
- #
406
- # Example:
407
- #
408
- # <tt>criteria.where(:field1 => "value1", :field2 => 15)</tt>
409
- #
410
- # <tt>criteria.where('this.a > 3')</tt>
411
- #
412
- # Returns: <tt>self</tt>
413
- def where(selector_or_js = {})
414
- case selector_or_js
415
- when String
416
- selector['$where'] = selector_or_js
417
- else
418
- selector.merge!(selector_or_js)
419
- end
420
- self
421
- end
422
- alias :and :where
423
- alias :conditions :where
424
-
425
- # Translate the supplied arguments into a +Criteria+ object.
426
- #
427
- # If the passed in args is a single +String+, then it will
428
- # construct an id +Criteria+ from it.
429
- #
430
- # If the passed in args are a type and a hash, then it will construct
431
- # the +Criteria+ with the proper selector, options, and type.
432
- #
433
- # Options:
434
- #
435
- # args: either a +String+ or a +Symbol+, +Hash combination.
436
- #
437
- # Example:
438
- #
439
- # <tt>Criteria.translate(Person, "4ab2bc4b8ad548971900005c")</tt>
440
- #
441
- # <tt>Criteria.translate(Person, :conditions => { :field => "value"}, :limit => 20)</tt>
442
- #
443
- # Returns a new +Criteria+ object.
444
- def self.translate(klass, params = {})
445
- return new(klass).id(params).one unless params.is_a?(Hash)
446
- return new(klass).criteria(params)
447
- end
448
-
449
- protected
450
- # Execute the criteria. This will take the internally built selector and options
451
- # and pass them on to the Ruby driver's +find()+ method on the collection. The
452
- # collection itself will be retrieved from the class provided.
453
- #
454
- # Returns either a cursor or an empty array.
455
- def execute
456
- cursor = klass.collection.find(selector, options.dup)
457
- if cursor
458
- @count = cursor.count
459
- cursor
460
- else
461
- []
462
- end
463
- end
464
-
465
- # Filters the unused options out of the options +Hash+. Currently this
466
- # takes into account the "page" and "per_page" options that would be passed
467
- # in if using will_paginate.
468
- def filter_options
469
- page_num = options.delete(:page)
470
- per_page_num = options.delete(:per_page)
471
- if (page_num || per_page_num)
472
- options[:limit] = (per_page_num || 20).to_i
473
- options[:skip] = (page_num || 1).to_i * options[:limit] - options[:limit]
474
- end
475
- end
476
-
477
- def self.invert(order)
478
- SORT_REVERSALS[order]
479
- end
1
+ require 'mongoid/extensions/hash/criteria_helpers'
2
+ require 'mongoid/extensions/symbol/inflections'
3
+ require 'mongodoc/matchers'
4
+ require 'mongodoc/contexts'
5
+ require 'mongoid/criteria'
6
+
7
+ module MongoDoc
8
+ module Criteria
9
+ # Create a criteria for this +Document+ class
10
+ #
11
+ # <tt>Person.criteria</tt>
12
+ def criteria
13
+ Mongoid::Criteria.new(self)
14
+ end
15
+
16
+ delegate \
17
+ :and,
18
+ :any_in,
19
+ :cache,
20
+ :enslave,
21
+ :excludes,
22
+ :extras,
23
+ :id,
24
+ :in,
25
+ :limit,
26
+ :not_in,
27
+ :offset,
28
+ :only,
29
+ :order_by,
30
+ :page,
31
+ :per_page,
32
+ :skip,
33
+ :where, :to => :criteria
480
34
  end
481
35
  end
36
+
37
+ Hash.send(:include, Mongoid::Extensions::Hash::CriteriaHelpers)
38
+ Symbol.send(:include, Mongoid::Extensions::Symbol::Inflections)