mongoid 0.9.6 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/VERSION +1 -1
  2. data/lib/mongoid.rb +2 -2
  3. data/lib/mongoid/associations.rb +16 -9
  4. data/lib/mongoid/associations/belongs_to.rb +29 -5
  5. data/lib/mongoid/associations/has_many.rb +12 -0
  6. data/lib/mongoid/associations/has_one.rb +29 -9
  7. data/lib/mongoid/associations/options.rb +5 -0
  8. data/lib/mongoid/associations/relates_to_many.rb +10 -0
  9. data/lib/mongoid/associations/relates_to_one.rb +24 -5
  10. data/lib/mongoid/commands.rb +8 -0
  11. data/lib/mongoid/commands/quick_save.rb +19 -0
  12. data/lib/mongoid/commands/save.rb +1 -1
  13. data/lib/mongoid/criteria.rb +8 -18
  14. data/lib/mongoid/document.rb +27 -59
  15. data/mongoid.gemspec +8 -9
  16. data/spec/integration/mongoid/associations_spec.rb +41 -0
  17. data/spec/integration/mongoid/document_spec.rb +0 -13
  18. data/spec/spec_helper.rb +1 -8
  19. data/spec/unit/mongoid/associations/belongs_to_spec.rb +34 -4
  20. data/spec/unit/mongoid/associations/has_many_spec.rb +9 -0
  21. data/spec/unit/mongoid/associations/has_one_spec.rb +23 -2
  22. data/spec/unit/mongoid/associations/options_spec.rb +12 -1
  23. data/spec/unit/mongoid/associations/relates_to_many_spec.rb +22 -0
  24. data/spec/unit/mongoid/associations/relates_to_one_spec.rb +65 -1
  25. data/spec/unit/mongoid/associations_spec.rb +53 -1
  26. data/spec/unit/mongoid/commands/quick_save_spec.rb +24 -0
  27. data/spec/unit/mongoid/commands/save_spec.rb +2 -2
  28. data/spec/unit/mongoid/commands_spec.rb +107 -102
  29. data/spec/unit/mongoid/criteria_spec.rb +33 -1
  30. metadata +7 -8
  31. data/lib/mongoid/associations/accessor.rb +0 -30
  32. data/lib/mongoid/associations/decorator.rb +0 -27
  33. data/spec/unit/mongoid/associations/accessor_spec.rb +0 -123
  34. data/spec/unit/mongoid/associations/decorator_spec.rb +0 -36
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.6
1
+ 0.9.7
@@ -80,12 +80,12 @@ module Mongoid
80
80
  # Sets the Mongo::DB to be used.
81
81
  def self.database=(db)
82
82
  raise InvalidDatabaseError.new("Database should be a Mongo::DB, not #{db.class.name}") unless db.kind_of?(Mongo::DB)
83
- @@database = db
83
+ @database = db
84
84
  end
85
85
 
86
86
  # Returns the Mongo::DB to use or raise an error if none was set.
87
87
  def self.database
88
- @@database || (raise InvalidDatabaseError.new("No database has been set"))
88
+ @database || (raise InvalidDatabaseError.new("No database has been set"))
89
89
  end
90
90
 
91
91
  end
@@ -1,6 +1,4 @@
1
1
  # encoding: utf-8
2
- require "mongoid/associations/decorator"
3
- require "mongoid/associations/accessor"
4
2
  require "mongoid/associations/belongs_to"
5
3
  require "mongoid/associations/has_many"
6
4
  require "mongoid/associations/has_one"
@@ -22,9 +20,15 @@ module Mongoid # :nodoc:
22
20
  self.class.associations
23
21
  end
24
22
 
25
- # Updates all the relational associations for the document.
23
+ # Updates all the one-to-many relational associations for the name.
26
24
  def update_associations(name)
27
- send(name).each { |doc| doc.save }
25
+ send(name).each { |doc| doc.quick_save }
26
+ end
27
+
28
+ # Update the one-to-one relational association for the name.
29
+ def update_association(name)
30
+ association = send(name)
31
+ association.quick_save if association
28
32
  end
29
33
  end
30
34
 
@@ -138,12 +142,15 @@ module Mongoid # :nodoc:
138
142
  # end
139
143
  #
140
144
  def relates_to_one(name, options = {})
141
- field "#{name.to_s}_id"
142
- index "#{name.to_s}_id"
145
+ key = name.to_s
146
+ field "#{key}_id"
143
147
  add_association(
144
148
  Associations::RelatesToOne,
145
149
  Associations::Options.new(options.merge(:name => name))
146
150
  )
151
+ before_save do |document|
152
+ document.update_association(name)
153
+ end
147
154
  end
148
155
 
149
156
  # Adds a relational association from the Document to many Documents in
@@ -162,7 +169,7 @@ module Mongoid # :nodoc:
162
169
  def relates_to_many(name, options = {})
163
170
  add_association(
164
171
  Associations::RelatesToMany,
165
- Associations::Options.new(options.merge(:name => name))
172
+ Associations::Options.new(options.merge(:name => name, :parent_key => self.name.foreign_key))
166
173
  )
167
174
  before_save do |document|
168
175
  document.update_associations(name)
@@ -177,11 +184,11 @@ module Mongoid # :nodoc:
177
184
  associations[name] = type
178
185
  define_method(name) do
179
186
  return instance_variable_get("@#{name}") if instance_variable_defined?("@#{name}")
180
- proxy = Associations::Accessor.get(type, self, options)
187
+ proxy = type.instantiate(self, options)
181
188
  instance_variable_set("@#{name}", proxy)
182
189
  end
183
190
  define_method("#{name}=") do |object|
184
- proxy = Associations::Accessor.set(type, self, object, options)
191
+ proxy = type.update(object, self, options)
185
192
  if instance_variable_defined?("@#{name}")
186
193
  remove_instance_variable("@#{name}")
187
194
  else
@@ -2,27 +2,51 @@
2
2
  module Mongoid #:nodoc:
3
3
  module Associations #:nodoc:
4
4
  class BelongsTo #:nodoc:
5
- include Decorator
6
5
 
7
- # Creates the new association by setting the internal
6
+ delegate :==, :to => :document
7
+ attr_reader :document, :options
8
+
9
+ # Creates the new association by setting the internal
8
10
  # document as the passed in Document. This should be the
9
11
  # parent.
10
12
  #
11
13
  # All method calls on this object will then be delegated
12
14
  # to the internal document itself.
15
+ #
16
+ # Options:
17
+ #
18
+ # document: The parent +Document+
19
+ # options: The association options
13
20
  def initialize(document, options)
14
- @document = document.parent
15
- decorate!
21
+ @document, @options = document, options
16
22
  end
17
23
 
18
24
  # Returns the parent document. The id param is present for
19
- # compatibility with rails, however this could be overwritten
25
+ # compatibility with rails, however this could be overwritten
20
26
  # in the future.
21
27
  def find(id)
22
28
  @document
23
29
  end
24
30
 
31
+ # Delegate all missing methods over to the parent +Document+.
32
+ def method_missing(name, *args)
33
+ @document.send(name, *args)
34
+ end
35
+
25
36
  class << self
37
+ # Creates the new association by setting the internal
38
+ # document as the passed in Document. This should be the
39
+ # parent.
40
+ #
41
+ # Options:
42
+ #
43
+ # document: The parent +Document+
44
+ # options: The association options
45
+ def instantiate(document, options)
46
+ parent = document.parent
47
+ parent.nil? ? nil : new(parent, options)
48
+ end
49
+
26
50
  # Returns the macro used to create the association.
27
51
  def macro
28
52
  :belongs_to
@@ -83,6 +83,18 @@ module Mongoid #:nodoc:
83
83
  end
84
84
 
85
85
  class << self
86
+
87
+ # Preferred method of creating a new +HasMany+ association. It will
88
+ # delegate to new.
89
+ #
90
+ # Options:
91
+ #
92
+ # document: The parent +Document+
93
+ # options: The association options
94
+ def instantiate(document, options)
95
+ new(document, options)
96
+ end
97
+
86
98
  # Returns the macro used to create the association.
87
99
  def macro
88
100
  :has_many
@@ -2,16 +2,13 @@
2
2
  module Mongoid #:nodoc:
3
3
  module Associations #:nodoc:
4
4
  class HasOne #:nodoc:
5
- include Decorator
6
5
 
7
- delegate :valid?, :to => :document
8
-
9
- attr_accessor :parent, :options
6
+ delegate :==, :to => :document
7
+ attr_reader :document, :parent, :options
10
8
 
11
9
  # Build a new object for the association.
12
10
  def build(attributes)
13
11
  @document = attributes.assimilate(@parent, @options)
14
- decorate!
15
12
  self
16
13
  end
17
14
 
@@ -28,14 +25,37 @@ module Mongoid #:nodoc:
28
25
  #
29
26
  # All method calls on this object will then be delegated
30
27
  # to the internal document itself.
31
- def initialize(document, options)
28
+ #
29
+ # Options:
30
+ #
31
+ # document: The parent +Document+
32
+ # attributes: The attributes of the decorated object.
33
+ # options: The association options.
34
+ def initialize(document, attributes, options)
32
35
  @parent, @options = document, options
33
- attributes = @parent.attributes[options.name]
34
- @document = (attributes || {}).assimilate(@parent, @options)
35
- decorate!
36
+ unless attributes.nil?
37
+ @document = attributes.assimilate(@parent, @options)
38
+ end
39
+ end
40
+
41
+ # Delegate all missing methods over to the +Document+.
42
+ def method_missing(name, *args)
43
+ @document.send(name, *args)
36
44
  end
37
45
 
38
46
  class << self
47
+ # Preferred method of instantiating a new +HasOne+, since nil values
48
+ # will be handled properly.
49
+ #
50
+ # Options:
51
+ #
52
+ # document: The parent +Document+
53
+ # options: The association options.
54
+ def instantiate(document, options)
55
+ attributes = document.attributes[options.name]
56
+ new(document, attributes, options)
57
+ end
58
+
39
59
  # Returns the macro used to create the association.
40
60
  def macro
41
61
  :has_one
@@ -32,6 +32,11 @@ module Mongoid #:nodoc:
32
32
  @attributes[:name]
33
33
  end
34
34
 
35
+ # Returns the parent foreign key association name.
36
+ def parent_key
37
+ @attributes[:parent_key]
38
+ end
39
+
35
40
  # Returns whether or not this association is polymorphic.
36
41
  def polymorphic
37
42
  @attributes[:polymorphic] == true
@@ -17,6 +17,16 @@ module Mongoid #:nodoc:
17
17
  end
18
18
 
19
19
  class << self
20
+ # Preferred method for creating the new +RelatesToMany+ association.
21
+ #
22
+ # Options:
23
+ #
24
+ # document: The +Document+ that contains the relationship.
25
+ # options: The association +Options+.
26
+ def instantiate(document, options)
27
+ new(document, options)
28
+ end
29
+
20
30
  # Returns the macro used to create the association.
21
31
  def macro
22
32
  :relates_to_many
@@ -2,7 +2,9 @@
2
2
  module Mongoid #:nodoc:
3
3
  module Associations #:nodoc:
4
4
  class RelatesToOne #:nodoc:
5
- include Decorator
5
+
6
+ delegate :==, :to => :document
7
+ attr_reader :document
6
8
 
7
9
  # Initializing a related association only requires looking up the object
8
10
  # by its id.
@@ -11,12 +13,29 @@ module Mongoid #:nodoc:
11
13
  #
12
14
  # document: The +Document+ that contains the relationship.
13
15
  # options: The association +Options+.
14
- def initialize(document, options)
15
- @document = options.klass.find(document.send(options.foreign_key))
16
- decorate!
16
+ def initialize(document, foreign_key, options)
17
+ @document = options.klass.find(foreign_key)
18
+ end
19
+
20
+ # Delegate all missing methods over to the +Document+.
21
+ def method_missing(name, *args)
22
+ @document.send(name, *args)
17
23
  end
18
24
 
19
25
  class << self
26
+ # Instantiate a new +RelatesToOne+ or return nil if the foreign key is
27
+ # nil. It is preferrable to use this method over the traditional call
28
+ # to new.
29
+ #
30
+ # Options:
31
+ #
32
+ # document: The +Document+ that contains the relationship.
33
+ # options: The association +Options+.
34
+ def instantiate(document, options)
35
+ foreign_key = document.send(options.foreign_key)
36
+ foreign_key.nil? ? nil : new(document, foreign_key, options)
37
+ end
38
+
20
39
  # Returns the macro used to create the association.
21
40
  def macro
22
41
  :relates_to_one
@@ -35,7 +54,7 @@ module Mongoid #:nodoc:
35
54
  #
36
55
  # <tt>RelatesToOne.update(game, person, options)</tt>
37
56
  def update(related, parent, options)
38
- parent.send("#{options.foreign_key}=", related.id)
57
+ parent.send("#{options.foreign_key}=", related.id); related
39
58
  end
40
59
  end
41
60
 
@@ -5,6 +5,7 @@ require "mongoid/commands/delete_all"
5
5
  require "mongoid/commands/destroy"
6
6
  require "mongoid/commands/destroy_all"
7
7
  require "mongoid/commands/save"
8
+ require "mongoid/commands/quick_save"
8
9
  require "mongoid/commands/validate"
9
10
 
10
11
  module Mongoid #:nodoc:
@@ -61,6 +62,13 @@ module Mongoid #:nodoc:
61
62
  end
62
63
  end
63
64
 
65
+ # Performs a save with no validations and no callbacks. This is used in
66
+ # relational association saves, and is not recommended for use by
67
+ # anything else.
68
+ def quick_save
69
+ QuickSave.execute(self)
70
+ end
71
+
64
72
  # Update the attributes of the +Document+. Will call save after the
65
73
  # attributes have been updated.
66
74
  def update_attributes(attrs = {})
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Commands
4
+ class QuickSave
5
+ # Performs a save of the supplied +Document+ without any validations or
6
+ # callbacks. This is a dangerous command only intended for internal use
7
+ # with saving relational associations.
8
+ #
9
+ # Options:
10
+ #
11
+ # doc: A +Document+ that is going to be persisted.
12
+ #
13
+ # Returns: true
14
+ def self.execute(doc)
15
+ doc.collection.save(doc.attributes)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -21,7 +21,7 @@ module Mongoid #:nodoc:
21
21
  collection ? collection.save(doc.attributes) : raise(MissingParentError.new(doc))
22
22
  end
23
23
  doc.run_callbacks :after_save
24
- return doc
24
+ return true
25
25
  end
26
26
  end
27
27
  end
@@ -110,13 +110,9 @@ module Mongoid #:nodoc:
110
110
  # Example:
111
111
  #
112
112
  # <tt>criteria.each { |doc| p doc }</tt>
113
- def each
113
+ def each(&block)
114
114
  @collection ||= execute
115
- if block_given?
116
- @collection.each { |doc| yield doc }
117
- else
118
- @collection.each
119
- end
115
+ block_given? ? @collection.each { |doc| yield doc } : self
120
116
  end
121
117
 
122
118
  # Adds a criterion to the +Criteria+ that specifies values that are not allowed
@@ -152,9 +148,7 @@ module Mongoid #:nodoc:
152
148
  #
153
149
  # Returns: <tt>self</tt>
154
150
  def extras(extras)
155
- @options = extras
156
- filter_options
157
- self
151
+ @options = extras; filter_options; self
158
152
  end
159
153
 
160
154
  # Return the first result for the +Criteria+.
@@ -176,8 +170,7 @@ module Mongoid #:nodoc:
176
170
  # Example:
177
171
  #
178
172
  # <tt>criteria.select(:field1).where(:field1 => "Title").group(Person)</tt>
179
- def group(klass = nil)
180
- @klass = klass if klass
173
+ def group
181
174
  @klass.collection.group(
182
175
  @options[:fields],
183
176
  @selector,
@@ -367,11 +360,8 @@ module Mongoid #:nodoc:
367
360
  # Either returns the page option and removes it from the options, or
368
361
  # returns a default value of 1.
369
362
  def page
370
- if @options[:skip] && @options[:limit]
371
- (@options[:skip].to_i + @options[:limit].to_i) / @options[:limit].to_i
372
- else
373
- 1
374
- end
363
+ skips, limits = @options[:skip], @options[:limit]
364
+ (skips && limits) ? (skips + limits) / limits : 1
375
365
  end
376
366
 
377
367
  # Executes the +Criteria+ and paginates the results.
@@ -505,8 +495,8 @@ module Mongoid #:nodoc:
505
495
  page_num = @options.delete(:page)
506
496
  per_page_num = @options.delete(:per_page)
507
497
  if (page_num || per_page_num)
508
- @options[:limit] = (per_page_num || 20).to_i
509
- @options[:skip] = (page_num || 1).to_i * @options[:limit] - @options[:limit]
498
+ @options[:limit] = limits = (per_page_num || 20).to_i
499
+ @options[:skip] = (page_num || 1).to_i * limits - limits
510
500
  end
511
501
  end
512
502
 
@@ -8,17 +8,10 @@ module Mongoid #:nodoc:
8
8
  attr_accessor :association_name, :parent
9
9
  attr_reader :attributes, :new_record
10
10
 
11
- define_callbacks \
12
- :after_create,
13
- :after_destroy,
14
- :after_save,
15
- :after_update,
16
- :after_validation,
17
- :before_create,
18
- :before_destroy,
19
- :before_save,
20
- :before_update,
21
- :before_validation
11
+ delegate :collection, :defaults, :embedded?, :fields, :primary_key, :to => :klass
12
+
13
+ define_callbacks :before_create, :before_destroy, :before_save, :before_update, :before_validation
14
+ define_callbacks :after_create, :after_destroy, :after_save, :after_update, :after_validation
22
15
 
23
16
  class << self
24
17
 
@@ -29,7 +22,7 @@ module Mongoid #:nodoc:
29
22
  # Returns: <tt>Mongo::Collection</tt>
30
23
  def collection
31
24
  return nil if embedded?
32
- @collection_name = self.to_s.demodulize.tableize
25
+ @collection_name ||= self.to_s.demodulize.tableize
33
26
  @collection ||= Mongoid.database.collection(@collection_name)
34
27
  end
35
28
 
@@ -56,11 +49,8 @@ module Mongoid #:nodoc:
56
49
  #
57
50
  # <tt>field :score, :default => 0</tt>
58
51
  def field(name, options = {})
59
- @fields ||= {}.with_indifferent_access
60
- @defaults ||= {}.with_indifferent_access
61
- @fields[name] = Field.new(name.to_s, options)
62
- @defaults[name] = options[:default] if options[:default]
63
- define_field_methods(name, options)
52
+ define(name, options)
53
+ default(name, options)
64
54
  end
65
55
 
66
56
  # Returns all the fields for the Document as a +Hash+ with names as keys.
@@ -107,12 +97,23 @@ module Mongoid #:nodoc:
107
97
  end
108
98
 
109
99
  protected
110
- def define_field_methods(name, options)
100
+
101
+ # Define a field attribute for the +Document+.
102
+ def define(name, options = {})
103
+ @fields ||= {}.with_indifferent_access
104
+ @fields[name] = Field.new(name.to_s, options)
111
105
  define_method(name) { read_attribute(name) }
112
106
  define_method("#{name}=") { |value| write_attribute(name, value) }
113
107
  define_method("#{name}?") { read_attribute(name) == true } if options[:type] == Boolean
114
108
  end
115
109
 
110
+ # Set up a default value for a field.
111
+ def default(name, options = {})
112
+ value = options[:default]
113
+ @defaults ||= {}.with_indifferent_access
114
+ @defaults[name] = value if value
115
+ end
116
+
116
117
  end
117
118
 
118
119
  # Performs equality checking on the attributes. For now we chack against
@@ -138,9 +139,7 @@ module Mongoid #:nodoc:
138
139
  #
139
140
  # Returns: The child +Document+.
140
141
  def assimilate(parent, options)
141
- parentize(parent, options.name)
142
- notify
143
- self
142
+ parentize(parent, options.name); notify; self
144
143
  end
145
144
 
146
145
  # Clone the current +Document+. This will return all attributes with the
@@ -149,26 +148,6 @@ module Mongoid #:nodoc:
149
148
  self.class.instantiate(@attributes.except(:_id).except(:versions).dup, true)
150
149
  end
151
150
 
152
- # Get the Mongo::Collection associated with this Document.
153
- def collection
154
- self.class.collection
155
- end
156
-
157
- # Returns the class defaults
158
- def defaults
159
- self.class.defaults
160
- end
161
-
162
- # Return true if the +Document+ is embedded in another +Document+.
163
- def embedded?
164
- self.class.embedded?
165
- end
166
-
167
- # Get the fields for the Document class.
168
- def fields
169
- self.class.fields
170
- end
171
-
172
151
  # Get the id associated with this object. This will pull the _id value out
173
152
  # of the attributes +Hash+.
174
153
  def id
@@ -209,22 +188,6 @@ module Mongoid #:nodoc:
209
188
  "#{self.class.name} : #{@attributes.inspect}"
210
189
  end
211
190
 
212
- # Return the +Document+ primary key. This will only exist if a key has been
213
- # set up on the +Document+ and will return an array of fields.
214
- #
215
- # Example:
216
- #
217
- # class Person < Mongoid::Document
218
- # field :first_name
219
- # field :last_name
220
- # key :first_name, :last_name
221
- # end
222
- #
223
- # <tt>person.primary_key #[:first_name, :last_name]</tt>
224
- def primary_key
225
- self.class.primary_key
226
- end
227
-
228
191
  # Returns true is the +Document+ has not been persisted to the database,
229
192
  # false if it has. This is determined by the instance variable @new_record
230
193
  # and NOT if the object has an id.
@@ -322,8 +285,8 @@ module Mongoid #:nodoc:
322
285
  # This will also cause the observing +Document+ to notify it's parent if
323
286
  # there is any.
324
287
  def update(child, clear = false)
325
- @attributes.insert(child.association_name, child.attributes) unless clear
326
- @attributes.delete(child.association_name) if clear
288
+ name = child.association_name
289
+ clear ? @attributes.delete(name) : @attributes.insert(name, child.attributes)
327
290
  notify
328
291
  end
329
292
 
@@ -377,5 +340,10 @@ module Mongoid #:nodoc:
377
340
  @attributes[:_id] = Mongo::ObjectID.new.to_s unless id
378
341
  end
379
342
  end
343
+
344
+ # Convenience method to get the document's class
345
+ def klass
346
+ self.class
347
+ end
380
348
  end
381
349
  end