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 CHANGED
@@ -1 +1 @@
1
- 0.8.10
1
+ 0.9.0
@@ -73,7 +73,7 @@ module Mongoid
73
73
  end
74
74
  end
75
75
 
76
- # Raised when invalid options are passed into an association.
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
@@ -14,8 +14,29 @@ module Mongoid #:nodoc:
14
14
  #
15
15
  # <tt>criteria.execute</tt>
16
16
  class Criteria
17
- attr_accessor :klass
18
- attr_reader :selector, :options, :type
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(klass = nil)
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(type, klass = nil)
200
- @selector, @options, @type, @klass = {}, {}, type, klass
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(Person)</tt>
282
- def paginate(klass = nil)
283
- results = execute(klass)
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(results)
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(:all, :conditions => { :field => "value"}, :limit => 20)</tt>
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
- type = args[0] || :all
432
+ klass = args[0]
350
433
  params = args[1] || {}
351
- return new(:first).id(args[0]) unless type.is_a?(Symbol)
352
- return new(type).where(params.delete(:conditions)).extras(params)
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
@@ -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(:all, *args)
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(self)
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
- Criteria.translate(*args).execute(self)
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(:first, *args)
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
- return find(:last, :conditions => {}, :sort => [[:_id, :desc]]) if args.empty?
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 = Criteria.translate(finder, :conditions => conditions).execute(self)
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(:all, params).paginate(self)
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(:all, self).select(*args)
126
+ Criteria.new(self).select(*args)
112
127
  end
113
128
 
114
129
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{mongoid}
8
- s.version = "0.8.10"
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-27}
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[0].title.should == "Test"
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(:all, { :per_page => 20, :page => 1 })
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