mongoid 0.8.10 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/mongoid.rb +1 -1
- data/lib/mongoid/criteria.rb +154 -48
- data/lib/mongoid/extensions/string/inflections.rb +13 -0
- data/lib/mongoid/extensions/symbol/inflections.rb +14 -0
- data/lib/mongoid/finders.rb +24 -9
- data/mongoid.gemspec +2 -2
- data/spec/integration/mongoid/document_spec.rb +19 -3
- data/spec/spec_helper.rb +16 -0
- data/spec/unit/mongoid/criteria_spec.rb +304 -97
- data/spec/unit/mongoid/extensions/string/inflections_spec.rb +35 -0
- data/spec/unit/mongoid/extensions/symbol/inflections_spec.rb +36 -0
- data/spec/unit/mongoid/finders_spec.rb +44 -47
- metadata +2 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.9.0
|
data/lib/mongoid.rb
CHANGED
@@ -73,7 +73,7 @@ module Mongoid
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
-
# Raised when invalid options are passed into
|
76
|
+
# Raised when invalid options are passed into a constructor.
|
77
77
|
class InvalidOptionsError < RuntimeError; end
|
78
78
|
|
79
79
|
# Connect to the database name supplied. This should be run
|
data/lib/mongoid/criteria.rb
CHANGED
@@ -14,8 +14,29 @@ module Mongoid #:nodoc:
|
|
14
14
|
#
|
15
15
|
# <tt>criteria.execute</tt>
|
16
16
|
class Criteria
|
17
|
-
|
18
|
-
|
17
|
+
include Enumerable
|
18
|
+
|
19
|
+
attr_reader :klass, :options, :selector
|
20
|
+
|
21
|
+
# Returns true if the supplied +Enumerable+ or +Criteria+ is equal to the results
|
22
|
+
# of this +Criteria+ or the criteria itself.
|
23
|
+
#
|
24
|
+
# This will force a database load when called if an enumerable is passed.
|
25
|
+
#
|
26
|
+
# Options:
|
27
|
+
#
|
28
|
+
# other: The other +Enumerable+ or +Criteria+ to compare to.
|
29
|
+
def ==(other)
|
30
|
+
case other
|
31
|
+
when Criteria
|
32
|
+
self.selector == other.selector && self.options == other.options
|
33
|
+
when Enumerable
|
34
|
+
execute
|
35
|
+
return (@collection == other)
|
36
|
+
else
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
end
|
19
40
|
|
20
41
|
AGGREGATE_REDUCE = "function(obj, prev) { prev.count++; }"
|
21
42
|
# Aggregate the criteria. This will take the internally built selector and options
|
@@ -54,21 +75,32 @@ module Mongoid #:nodoc:
|
|
54
75
|
|
55
76
|
# Get the count of matching documents in the database for the +Criteria+.
|
56
77
|
#
|
57
|
-
# Options:
|
58
|
-
#
|
59
|
-
# klass: Optional class that the collection will be retrieved from.
|
60
|
-
#
|
61
78
|
# Example:
|
62
79
|
#
|
63
80
|
# <tt>criteria.count</tt>
|
64
81
|
#
|
65
82
|
# Returns: <tt>Integer</tt>
|
66
|
-
def count
|
83
|
+
def count
|
67
84
|
return @count if @count
|
68
85
|
@klass = klass if klass
|
69
86
|
return @klass.collection.find(@selector, @options.dup).count
|
70
87
|
end
|
71
88
|
|
89
|
+
# Iterate over each +Document+ in the results. This can take an optional
|
90
|
+
# block to pass to each argument in the results.
|
91
|
+
#
|
92
|
+
# Example:
|
93
|
+
#
|
94
|
+
# <tt>criteria.each { |doc| p doc }</tt>
|
95
|
+
def each
|
96
|
+
@collection ||= execute
|
97
|
+
if block_given?
|
98
|
+
@collection.each { |doc| yield doc }
|
99
|
+
else
|
100
|
+
@collection.each
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
72
104
|
# Adds a criterion to the +Criteria+ that specifies values that are not allowed
|
73
105
|
# to match any document in the database. The MongoDB conditional operator that
|
74
106
|
# will be used is "$ne".
|
@@ -89,32 +121,6 @@ module Mongoid #:nodoc:
|
|
89
121
|
exclusions.each { |key, value| @selector[key] = { "$ne" => value } }; self
|
90
122
|
end
|
91
123
|
|
92
|
-
# Execute the criteria. This will take the internally built selector and options
|
93
|
-
# and pass them on to the Ruby driver's +find()+ method on the collection. The
|
94
|
-
# collection itself will be retrieved from the class provided, and once the
|
95
|
-
# query has returned new documents of the type of class provided will be instantiated.
|
96
|
-
#
|
97
|
-
# If this is a +Criteria+ to only find the first object, this will return a
|
98
|
-
# single object of the type of class provided.
|
99
|
-
#
|
100
|
-
# If this is a +Criteria+ to find multiple results, will return an +Array+ of
|
101
|
-
# objects of the type of class provided.
|
102
|
-
def execute(klass = nil)
|
103
|
-
@klass = klass if klass
|
104
|
-
if type == :all
|
105
|
-
attributes = @klass.collection.find(@selector, @options.dup)
|
106
|
-
if attributes
|
107
|
-
@count = attributes.count
|
108
|
-
return attributes.collect { |doc| @klass.instantiate(doc) }
|
109
|
-
else
|
110
|
-
return []
|
111
|
-
end
|
112
|
-
else
|
113
|
-
attributes = @klass.collection.find_one(@selector, @options.dup)
|
114
|
-
return attributes ? @klass.instantiate(attributes) : nil
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
124
|
# Adds a criterion to the +Criteria+ that specifies additional options
|
119
125
|
# to be passed to the Ruby driver, in the exact format for the driver.
|
120
126
|
#
|
@@ -133,6 +139,16 @@ module Mongoid #:nodoc:
|
|
133
139
|
self
|
134
140
|
end
|
135
141
|
|
142
|
+
# Return the first result for the +Criteria+.
|
143
|
+
#
|
144
|
+
# Example:
|
145
|
+
#
|
146
|
+
# <tt>Criteria.select(:name).where(:name = "Chrissy").one</tt>
|
147
|
+
def one
|
148
|
+
attributes = @klass.collection.find_one(@selector, @options.dup)
|
149
|
+
attributes ? @klass.instantiate(attributes) : nil
|
150
|
+
end
|
151
|
+
|
136
152
|
GROUP_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
|
137
153
|
# Groups the criteria. This will take the internally built selector and options
|
138
154
|
# and pass them on to the Ruby driver's +group()+ method on the collection. The
|
@@ -196,8 +212,24 @@ module Mongoid #:nodoc:
|
|
196
212
|
#
|
197
213
|
# type: One of :all, :first:, or :last
|
198
214
|
# klass: The class to execute on.
|
199
|
-
def initialize(
|
200
|
-
@selector, @options, @
|
215
|
+
def initialize(klass)
|
216
|
+
@selector, @options, @klass = {}, {}, klass
|
217
|
+
end
|
218
|
+
|
219
|
+
# Return the last result for the +Criteria+. Essentially does a find_one on
|
220
|
+
# the collection with the sorting reversed. If no sorting parameters have
|
221
|
+
# been provided it will default to ids.
|
222
|
+
#
|
223
|
+
# Example:
|
224
|
+
#
|
225
|
+
# <tt>Criteria.select(:name).where(:name = "Chrissy").last</tt>
|
226
|
+
def last
|
227
|
+
opts = @options.dup
|
228
|
+
sorting = opts[:sort]
|
229
|
+
sorting = [[:_id, :asc]] unless sorting
|
230
|
+
opts[:sort] = sorting.collect { |option| [ option[0], option[1].invert ] }
|
231
|
+
attributes = @klass.collection.find_one(@selector, opts)
|
232
|
+
attributes ? @klass.instantiate(attributes) : nil
|
201
233
|
end
|
202
234
|
|
203
235
|
# Adds a criterion to the +Criteria+ that specifies the maximum number of
|
@@ -217,6 +249,61 @@ module Mongoid #:nodoc:
|
|
217
249
|
@options[:limit] = value; self
|
218
250
|
end
|
219
251
|
|
252
|
+
# Merges another object into this +Criteria+. The other object may be a
|
253
|
+
# +Criteria+ or a +Hash+. This is used to combine multiple scopes together,
|
254
|
+
# where a chained scope situation may be desired.
|
255
|
+
#
|
256
|
+
# Options:
|
257
|
+
#
|
258
|
+
# other: The +Criteria+ or +Hash+ to merge with.
|
259
|
+
#
|
260
|
+
# Example:
|
261
|
+
#
|
262
|
+
# <tt>criteria.merge({ :conditions => { :title => "Sir" } })</tt>
|
263
|
+
def merge(other)
|
264
|
+
case other
|
265
|
+
when Hash
|
266
|
+
merge(self.class.translate(@klass, other))
|
267
|
+
else
|
268
|
+
@selector.update(other.selector)
|
269
|
+
@options.update(other.options)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# Used for chaining +Criteria+ scopes together in the for of class methods
|
274
|
+
# on the +Document+ the criteria is for.
|
275
|
+
#
|
276
|
+
# Options:
|
277
|
+
#
|
278
|
+
# name: The name of the class method on the +Document+ to chain.
|
279
|
+
# args: The arguments passed to the method.
|
280
|
+
#
|
281
|
+
# Example:
|
282
|
+
#
|
283
|
+
# class Person < Mongoid::Document
|
284
|
+
# field :title
|
285
|
+
# field :terms, :type => Boolean, :default => false
|
286
|
+
#
|
287
|
+
# class << self
|
288
|
+
# def knights
|
289
|
+
# all(:conditions => { :title => "Sir" })
|
290
|
+
# end
|
291
|
+
#
|
292
|
+
# def accepted
|
293
|
+
# all(:conditions => { :terms => true })
|
294
|
+
# end
|
295
|
+
# end
|
296
|
+
# end
|
297
|
+
#
|
298
|
+
# Person.accepted.knights #returns a merged criteria of the 2 scopes.
|
299
|
+
#
|
300
|
+
# Returns: <tt>Criteria</tt>
|
301
|
+
def method_missing(name, *args)
|
302
|
+
new_scope = @klass.send(name)
|
303
|
+
new_scope.merge(self)
|
304
|
+
new_scope
|
305
|
+
end
|
306
|
+
|
220
307
|
# Adds a criterion to the +Criteria+ that specifies values where none
|
221
308
|
# should match in order to return results. This is similar to an SQL "NOT IN"
|
222
309
|
# clause. The MongoDB conditional operator that will be used is "$nin".
|
@@ -272,17 +359,13 @@ module Mongoid #:nodoc:
|
|
272
359
|
|
273
360
|
# Executes the +Criteria+ and paginates the results.
|
274
361
|
#
|
275
|
-
# Options:
|
276
|
-
#
|
277
|
-
# klass: Optional class name to execute the criteria on.
|
278
|
-
#
|
279
362
|
# Example:
|
280
363
|
#
|
281
|
-
# <tt>criteria.paginate
|
282
|
-
def paginate
|
283
|
-
|
364
|
+
# <tt>criteria.paginate</tt>
|
365
|
+
def paginate
|
366
|
+
@collection ||= execute
|
284
367
|
WillPaginate::Collection.create(page, per_page, count) do |pager|
|
285
|
-
pager.replace(
|
368
|
+
pager.replace(@collection)
|
286
369
|
end
|
287
370
|
end
|
288
371
|
|
@@ -340,16 +423,16 @@ module Mongoid #:nodoc:
|
|
340
423
|
#
|
341
424
|
# Example:
|
342
425
|
#
|
343
|
-
# <tt>Criteria.translate("4ab2bc4b8ad548971900005c")</tt>
|
426
|
+
# <tt>Criteria.translate(Person, "4ab2bc4b8ad548971900005c")</tt>
|
344
427
|
#
|
345
|
-
# <tt>Criteria.translate(
|
428
|
+
# <tt>Criteria.translate(Person, :conditions => { :field => "value"}, :limit => 20)</tt>
|
346
429
|
#
|
347
430
|
# Returns a new +Criteria+ object.
|
348
431
|
def self.translate(*args)
|
349
|
-
|
432
|
+
klass = args[0]
|
350
433
|
params = args[1] || {}
|
351
|
-
return new(
|
352
|
-
return new(
|
434
|
+
return new(klass).id(params).one if params.is_a?(String)
|
435
|
+
return new(klass).where(params.delete(:conditions) || {}).extras(params)
|
353
436
|
end
|
354
437
|
|
355
438
|
# Adds a criterion to the +Criteria+ that specifies values that must
|
@@ -372,6 +455,29 @@ module Mongoid #:nodoc:
|
|
372
455
|
end
|
373
456
|
|
374
457
|
protected
|
458
|
+
# Execute the criteria. This will take the internally built selector and options
|
459
|
+
# and pass them on to the Ruby driver's +find()+ method on the collection. The
|
460
|
+
# collection itself will be retrieved from the class provided, and once the
|
461
|
+
# query has returned new documents of the type of class provided will be instantiated.
|
462
|
+
#
|
463
|
+
# If this is a +Criteria+ to only find the first object, this will return a
|
464
|
+
# single object of the type of class provided.
|
465
|
+
#
|
466
|
+
# If this is a +Criteria+ to find multiple results, will return an +Array+ of
|
467
|
+
# objects of the type of class provided.
|
468
|
+
def execute
|
469
|
+
attributes = @klass.collection.find(@selector, @options.dup)
|
470
|
+
if attributes
|
471
|
+
@count = attributes.count
|
472
|
+
@collection = attributes.collect { |doc| @klass.instantiate(doc) }
|
473
|
+
else
|
474
|
+
@collection = []
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
# Filters the unused options out of the options +Hash+. Currently this
|
479
|
+
# takes into account the "page" and "per_page" options that would be passed
|
480
|
+
# in if using will_paginate.
|
375
481
|
def filter_options
|
376
482
|
page_num = @options.delete(:page)
|
377
483
|
per_page_num = @options.delete(:per_page)
|
@@ -2,9 +2,22 @@ module Mongoid #:nodoc:
|
|
2
2
|
module Extensions #:nodoc:
|
3
3
|
module String #:nodoc:
|
4
4
|
module Inflections #:nodoc:
|
5
|
+
|
6
|
+
REVERSALS = {
|
7
|
+
"asc" => "desc",
|
8
|
+
"ascending" => "descending",
|
9
|
+
"desc" => "asc",
|
10
|
+
"descending" => "ascending"
|
11
|
+
}
|
12
|
+
|
13
|
+
def invert
|
14
|
+
REVERSALS[self]
|
15
|
+
end
|
16
|
+
|
5
17
|
def singular?
|
6
18
|
singularize == self
|
7
19
|
end
|
20
|
+
|
8
21
|
def plural?
|
9
22
|
pluralize == self
|
10
23
|
end
|
@@ -2,12 +2,26 @@ module Mongoid #:nodoc:
|
|
2
2
|
module Extensions #:nodoc:
|
3
3
|
module Symbol #:nodoc:
|
4
4
|
module Inflections #:nodoc:
|
5
|
+
|
6
|
+
REVERSALS = {
|
7
|
+
:asc => :desc,
|
8
|
+
:ascending => :descending,
|
9
|
+
:desc => :asc,
|
10
|
+
:descending => :ascending
|
11
|
+
}
|
12
|
+
|
13
|
+
def invert
|
14
|
+
REVERSALS[self]
|
15
|
+
end
|
16
|
+
|
5
17
|
def singular?
|
6
18
|
to_s.singular?
|
7
19
|
end
|
20
|
+
|
8
21
|
def plural?
|
9
22
|
to_s.plural?
|
10
23
|
end
|
24
|
+
|
11
25
|
end
|
12
26
|
end
|
13
27
|
end
|
data/lib/mongoid/finders.rb
CHANGED
@@ -8,7 +8,7 @@ module Mongoid #:nodoc:
|
|
8
8
|
#
|
9
9
|
# <tt>Person.all(:conditions => { :attribute => "value" })</tt>
|
10
10
|
def all(*args)
|
11
|
-
find(
|
11
|
+
find(*args)
|
12
12
|
end
|
13
13
|
|
14
14
|
# Returns a count of matching records in the database based on the
|
@@ -16,7 +16,16 @@ module Mongoid #:nodoc:
|
|
16
16
|
#
|
17
17
|
# <tt>Person.count(:first, :conditions => { :attribute => "value" })</tt>
|
18
18
|
def count(*args)
|
19
|
-
Criteria.translate(*args).count
|
19
|
+
Criteria.translate(self, *args).count
|
20
|
+
end
|
21
|
+
|
22
|
+
# Helper to initialize a new +Criteria+ object for this class.
|
23
|
+
#
|
24
|
+
# Example:
|
25
|
+
#
|
26
|
+
# <tt>Person.criteria</tt>
|
27
|
+
def criteria
|
28
|
+
Criteria.new(self)
|
20
29
|
end
|
21
30
|
|
22
31
|
# Find a +Document+ in several different ways.
|
@@ -33,7 +42,14 @@ module Mongoid #:nodoc:
|
|
33
42
|
#
|
34
43
|
# <tt>Person.find(Mongo::ObjectID.new.to_s)</tt>
|
35
44
|
def find(*args)
|
36
|
-
|
45
|
+
type = args.delete_at(0) if args[0].is_a?(Symbol)
|
46
|
+
criteria = Criteria.translate(self, *args)
|
47
|
+
case type
|
48
|
+
when :first then return criteria.one
|
49
|
+
when :last then return criteria.last
|
50
|
+
else
|
51
|
+
return criteria
|
52
|
+
end
|
37
53
|
end
|
38
54
|
|
39
55
|
# Find the first +Document+ given the conditions.
|
@@ -44,7 +60,7 @@ module Mongoid #:nodoc:
|
|
44
60
|
#
|
45
61
|
# <tt>Person.first(:conditions => { :attribute => "value" })</tt>
|
46
62
|
def first(*args)
|
47
|
-
find(
|
63
|
+
find(*args).one
|
48
64
|
end
|
49
65
|
|
50
66
|
# Find the last +Document+ given the conditions.
|
@@ -55,8 +71,7 @@ module Mongoid #:nodoc:
|
|
55
71
|
#
|
56
72
|
# <tt>Person.last(:conditions => { :attribute => "value" })</tt>
|
57
73
|
def last(*args)
|
58
|
-
|
59
|
-
return find(:last, *args) unless args.empty?
|
74
|
+
find(*args).last
|
60
75
|
end
|
61
76
|
|
62
77
|
# Will execute a +Criteria+ based on the +DynamicFinder+ that gets
|
@@ -73,7 +88,7 @@ module Mongoid #:nodoc:
|
|
73
88
|
def method_missing(name, *args)
|
74
89
|
dyna = DynamicFinder.new(name, *args)
|
75
90
|
finder, conditions = dyna.finder, dyna.conditions
|
76
|
-
results =
|
91
|
+
results = find(finder, :conditions => conditions)
|
77
92
|
results ? results : dyna.create(self)
|
78
93
|
end
|
79
94
|
|
@@ -91,7 +106,7 @@ module Mongoid #:nodoc:
|
|
91
106
|
#
|
92
107
|
# Returns paginated array of docs.
|
93
108
|
def paginate(params = {})
|
94
|
-
Criteria.translate(
|
109
|
+
Criteria.translate(self, params).paginate
|
95
110
|
end
|
96
111
|
|
97
112
|
# Entry point for creating a new criteria from a Document. This will
|
@@ -108,7 +123,7 @@ module Mongoid #:nodoc:
|
|
108
123
|
#
|
109
124
|
# Returns: <tt>Criteria</tt>
|
110
125
|
def select(*args)
|
111
|
-
Criteria.new(
|
126
|
+
Criteria.new(self).select(*args)
|
112
127
|
end
|
113
128
|
|
114
129
|
end
|
data/mongoid.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{mongoid}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.9.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Durran Jordan"]
|
12
|
-
s.date = %q{2009-11-
|
12
|
+
s.date = %q{2009-11-28}
|
13
13
|
s.email = %q{durran@gmail.com}
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"README.textile"
|
@@ -64,6 +64,23 @@ describe Mongoid::Document do
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
+
context "chaining criteria scopes" do
|
68
|
+
|
69
|
+
before do
|
70
|
+
@one = Person.create(:title => "Mr", :age => 55, :terms => true)
|
71
|
+
@two = Person.create(:title => "Sir", :age => 55, :terms => true)
|
72
|
+
@three = Person.create(:title => "Sir", :age => 35, :terms => true)
|
73
|
+
@four = Person.create(:title => "Sir", :age => 55, :terms => false)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "finds by the merged criteria" do
|
77
|
+
people = Person.old.accepted.knight
|
78
|
+
people.count.should == 1
|
79
|
+
people.first.should == @two
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
67
84
|
context "using dynamic finders" do
|
68
85
|
|
69
86
|
before do
|
@@ -113,7 +130,7 @@ describe Mongoid::Document do
|
|
113
130
|
|
114
131
|
it "returns an array of documents based on the selector provided" do
|
115
132
|
documents = Person.find(:all, :conditions => { :title => "Test"})
|
116
|
-
documents
|
133
|
+
documents.first.title.should == "Test"
|
117
134
|
end
|
118
135
|
|
119
136
|
end
|
@@ -181,8 +198,7 @@ describe Mongoid::Document do
|
|
181
198
|
end
|
182
199
|
|
183
200
|
it "returns a proper count" do
|
184
|
-
@criteria = Mongoid::Criteria.translate(
|
185
|
-
@criteria.execute(Person)
|
201
|
+
@criteria = Mongoid::Criteria.translate(Person, { :per_page => 20, :page => 1 })
|
186
202
|
@criteria.count.should == 30
|
187
203
|
end
|
188
204
|
|