mongoid 0.8.10 → 0.9.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.
- 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
|
|