mongodoc 0.2.2 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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)