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 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