mongoid 0.9.8 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/README.textile +5 -4
  2. data/VERSION +1 -1
  3. data/lib/mongoid.rb +3 -28
  4. data/lib/mongoid/associations.rb +63 -44
  5. data/lib/mongoid/associations/{relates_to_one.rb → belongs_to_related.rb} +4 -4
  6. data/lib/mongoid/associations/has_many.rb +26 -12
  7. data/lib/mongoid/associations/has_many_related.rb +100 -0
  8. data/lib/mongoid/associations/has_one.rb +13 -2
  9. data/lib/mongoid/associations/{relates_to_many.rb → has_one_related.rb} +34 -9
  10. data/lib/mongoid/attributes.rb +108 -8
  11. data/lib/mongoid/commands.rb +3 -11
  12. data/lib/mongoid/commands/save.rb +2 -6
  13. data/lib/mongoid/document.rb +6 -70
  14. data/lib/mongoid/errors.rb +34 -0
  15. data/mongoid.gemspec +14 -11
  16. data/spec/integration/mongoid/associations_spec.rb +19 -14
  17. data/spec/spec_helper.rb +6 -3
  18. data/spec/unit/mongoid/associations/belongs_to_related_spec.rb +112 -0
  19. data/spec/unit/mongoid/associations/has_many_related_spec.rb +292 -0
  20. data/spec/unit/mongoid/associations/has_many_spec.rb +17 -0
  21. data/spec/unit/mongoid/associations/has_one_related_spec.rb +130 -0
  22. data/spec/unit/mongoid/associations/has_one_spec.rb +53 -0
  23. data/spec/unit/mongoid/associations_spec.rb +36 -8
  24. data/spec/unit/mongoid/attributes_spec.rb +218 -0
  25. data/spec/unit/mongoid/commands/save_spec.rb +4 -2
  26. data/spec/unit/mongoid/commands_spec.rb +4 -17
  27. data/spec/unit/mongoid/criteria_spec.rb +0 -5
  28. data/spec/unit/mongoid/document_spec.rb +26 -156
  29. data/spec/unit/mongoid/errors_spec.rb +87 -0
  30. metadata +14 -11
  31. data/lib/mongoid/commands/quick_save.rb +0 -19
  32. data/spec/unit/mongoid/associations/relates_to_many_spec.rb +0 -76
  33. data/spec/unit/mongoid/associations/relates_to_one_spec.rb +0 -112
  34. data/spec/unit/mongoid/commands/quick_save_spec.rb +0 -24
@@ -44,11 +44,11 @@ Example of a simple domain model:
44
44
  field :title
45
45
  field :age, :type => Integer, :default => 0
46
46
 
47
- has_many :addresses
48
47
  has_one :name
48
+ has_many :addresses
49
49
 
50
- relates_to_one :game
51
- relates_to_many :posts
50
+ has_one_related :game
51
+ has_many_related :posts
52
52
  end
53
53
 
54
54
  class Address < Mongoid::Document
@@ -71,10 +71,11 @@ Example of a simple domain model:
71
71
  field :title
72
72
  field :text
73
73
  field :tags, :type => Array
74
- relates_to_one :person
74
+ belongs_to_related :person
75
75
  end
76
76
 
77
77
  class Game < ActiveRecord::Base
78
+ belongs_to_related :person
78
79
  end
79
80
  </pre>
80
81
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.8
1
+ 0.9.9
@@ -43,6 +43,7 @@ require "mongoid/commands"
43
43
  require "mongoid/criteria"
44
44
  require "mongoid/dynamic_finder"
45
45
  require "mongoid/extensions"
46
+ require "mongoid/errors"
46
47
  require "mongoid/field"
47
48
  require "mongoid/finders"
48
49
  require "mongoid/timestamps"
@@ -51,41 +52,15 @@ require "mongoid/document"
51
52
 
52
53
  module Mongoid
53
54
 
54
- # Raised when the database connection has not been set up.
55
- class InvalidDatabaseError < RuntimeError; end
56
-
57
- # Raised when invalid options are passed into a constructor.
58
- class InvalidOptionsError < RuntimeError; end
59
-
60
- # Raised when an association is defined on the class, but the
61
- # attribute in the hash is not an Array or Hash, or when
62
- # checking equality on objects of different types.
63
- class TypeMismatchError < RuntimeError; end
64
-
65
- # Raised when a persisence method ending in ! fails validation.
66
- class ValidationsError < RuntimeError; end
67
-
68
- # A common case of errors is to instantiate a child document without a
69
- # reference to a parent, which will result in trying to save on a nil
70
- # collection. This error is raised to help debug the issue.
71
- class MissingParentError < RuntimeError
72
- def initialize(doc)
73
- @document = doc
74
- end
75
- def message
76
- "Attempted to save embedded document #{@document.class.name}, but there was no associated parent"
77
- end
78
- end
79
-
80
55
  # Sets the Mongo::DB to be used.
81
56
  def self.database=(db)
82
- raise InvalidDatabaseError.new("Database should be a Mongo::DB, not #{db.class.name}") unless db.kind_of?(Mongo::DB)
57
+ raise Errors::InvalidDatabase.new("Database should be a Mongo::DB, not #{db.class.name}") unless db.kind_of?(Mongo::DB)
83
58
  @database = db
84
59
  end
85
60
 
86
61
  # Returns the Mongo::DB to use or raise an error if none was set.
87
62
  def self.database
88
- @database || (raise InvalidDatabaseError.new("No database has been set"))
63
+ @database || (raise Errors::InvalidDatabase.new("No database has been set, please use Mongoid.database="))
89
64
  end
90
65
 
91
66
  end
@@ -1,9 +1,10 @@
1
1
  # encoding: utf-8
2
2
  require "mongoid/associations/belongs_to"
3
+ require "mongoid/associations/belongs_to_related"
3
4
  require "mongoid/associations/has_many"
5
+ require "mongoid/associations/has_many_related"
4
6
  require "mongoid/associations/has_one"
5
- require "mongoid/associations/relates_to_many"
6
- require "mongoid/associations/relates_to_one"
7
+ require "mongoid/associations/has_one_related"
7
8
 
8
9
  module Mongoid # :nodoc:
9
10
  module Associations #:nodoc:
@@ -22,13 +23,13 @@ module Mongoid # :nodoc:
22
23
 
23
24
  # Updates all the one-to-many relational associations for the name.
24
25
  def update_associations(name)
25
- send(name).each { |doc| doc.quick_save }
26
+ send(name).each { |doc| doc.save }
26
27
  end
27
28
 
28
29
  # Update the one-to-one relational association for the name.
29
30
  def update_association(name)
30
31
  association = send(name)
31
- association.quick_save if association
32
+ association.save unless association.nil?
32
33
  end
33
34
  end
34
35
 
@@ -56,7 +57,7 @@ module Mongoid # :nodoc:
56
57
  # end
57
58
  def belongs_to(name, options = {})
58
59
  unless options.has_key?(:inverse_of)
59
- raise InvalidOptionsError.new("Options for belongs_to association must include :inverse_of")
60
+ raise Errors::InvalidOptions.new("Options for belongs_to association must include :inverse_of")
60
61
  end
61
62
  @embedded = true
62
63
  add_association(
@@ -65,6 +66,27 @@ module Mongoid # :nodoc:
65
66
  )
66
67
  end
67
68
 
69
+ # Adds a relational association from the child Document to a Document in
70
+ # another database or collection.
71
+ #
72
+ # Options:
73
+ #
74
+ # name: A +Symbol+ that is the related class name.
75
+ #
76
+ # Example:
77
+ #
78
+ # class Game < Mongoid::Document
79
+ # belongs_to_related :person
80
+ # end
81
+ #
82
+ def belongs_to_related(name, options = {})
83
+ field "#{name.to_s}_id"
84
+ add_association(
85
+ Associations::BelongsToRelated,
86
+ Associations::Options.new(options.merge(:name => name))
87
+ )
88
+ end
89
+
68
90
  # Adds the association from a parent document to its children. The name
69
91
  # of the association needs to be a pluralized form of the child class
70
92
  # name.
@@ -89,6 +111,29 @@ module Mongoid # :nodoc:
89
111
  )
90
112
  end
91
113
 
114
+ # Adds a relational association from the Document to many Documents in
115
+ # another database or collection.
116
+ #
117
+ # Options:
118
+ #
119
+ # name: A +Symbol+ that is the related class name pluralized.
120
+ #
121
+ # Example:
122
+ #
123
+ # class Person < Mongoid::Document
124
+ # has_many_related :posts
125
+ # end
126
+ #
127
+ def has_many_related(name, options = {})
128
+ add_association(
129
+ Associations::HasManyRelated,
130
+ Associations::Options.new(options.merge(:name => name, :parent_key => self.name.foreign_key))
131
+ )
132
+ before_save do |document|
133
+ document.update_associations(name)
134
+ end
135
+ end
136
+
92
137
  # Adds the association from a parent document to its child. The name
93
138
  # of the association needs to be a singular form of the child class
94
139
  # name.
@@ -113,67 +158,41 @@ module Mongoid # :nodoc:
113
158
  )
114
159
  end
115
160
 
116
- # Returns the macro associated with the supplied association name. This
117
- # will return has_one, has_many, belongs_to or nil.
118
- #
119
- # Options:
120
- #
121
- # name: The association name.
122
- #
123
- # Example:
124
- #
125
- # <tt>Person.reflect_on_association(:addresses)</tt>
126
- def reflect_on_association(name)
127
- association = associations[name]
128
- association ? association.macro : nil
129
- end
130
-
131
- # Adds a relational association from the Document to a Document in
161
+ # Adds a relational association from the Document to one Document in
132
162
  # another database or collection.
133
163
  #
134
164
  # Options:
135
165
  #
136
- # name: A +Symbol+ that is the related class name.
166
+ # name: A +Symbol+ that is the related class name pluralized.
137
167
  #
138
168
  # Example:
139
169
  #
140
170
  # class Person < Mongoid::Document
141
- # relates_to_one :game
171
+ # has_one_related :game
142
172
  # end
143
- #
144
- def relates_to_one(name, options = {})
145
- key = name.to_s
146
- field "#{key}_id"
173
+ def has_one_related(name, options = {})
147
174
  add_association(
148
- Associations::RelatesToOne,
149
- Associations::Options.new(options.merge(:name => name))
175
+ Associations::HasOneRelated,
176
+ Associations::Options.new(options.merge(:name => name, :parent_key => self.name.foreign_key))
150
177
  )
151
178
  before_save do |document|
152
179
  document.update_association(name)
153
180
  end
154
181
  end
155
182
 
156
- # Adds a relational association from the Document to many Documents in
157
- # another database or collection.
183
+ # Returns the macro associated with the supplied association name. This
184
+ # will return has_one, has_many, belongs_to or nil.
158
185
  #
159
186
  # Options:
160
187
  #
161
- # name: A +Symbol+ that is the related class name pluralized.
188
+ # name: The association name.
162
189
  #
163
190
  # Example:
164
191
  #
165
- # class Person < Mongoid::Document
166
- # relates_to_many :posts
167
- # end
168
- #
169
- def relates_to_many(name, options = {})
170
- add_association(
171
- Associations::RelatesToMany,
172
- Associations::Options.new(options.merge(:name => name, :parent_key => self.name.foreign_key))
173
- )
174
- before_save do |document|
175
- document.update_associations(name)
176
- end
192
+ # <tt>Person.reflect_on_association(:addresses)</tt>
193
+ def reflect_on_association(name)
194
+ association = associations[name]
195
+ association ? association.macro : nil
177
196
  end
178
197
 
179
198
  protected
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid #:nodoc:
3
3
  module Associations #:nodoc:
4
- class RelatesToOne #:nodoc:
4
+ class BelongsToRelated #:nodoc:
5
5
 
6
6
  delegate :==, :to => :document
7
7
  attr_reader :document
@@ -23,7 +23,7 @@ module Mongoid #:nodoc:
23
23
  end
24
24
 
25
25
  class << self
26
- # Instantiate a new +RelatesToOne+ or return nil if the foreign key is
26
+ # Instantiate a new +BelongsToRelated+ or return nil if the foreign key is
27
27
  # nil. It is preferrable to use this method over the traditional call
28
28
  # to new.
29
29
  #
@@ -38,7 +38,7 @@ module Mongoid #:nodoc:
38
38
 
39
39
  # Returns the macro used to create the association.
40
40
  def macro
41
- :relates_to_one
41
+ :belongs_to_related
42
42
  end
43
43
 
44
44
  # Perform an update of the relationship of the parent and child. This
@@ -52,7 +52,7 @@ module Mongoid #:nodoc:
52
52
  #
53
53
  # Example:
54
54
  #
55
- # <tt>RelatesToOne.update(game, person, options)</tt>
55
+ # <tt>BelongsToRelated.update(game, person, options)</tt>
56
56
  def update(related, parent, options)
57
57
  parent.send("#{options.foreign_key}=", related.id); related
58
58
  end
@@ -7,10 +7,12 @@ module Mongoid #:nodoc:
7
7
 
8
8
  # Appends the object to the +Array+, setting its parent in
9
9
  # the process.
10
- def <<(object)
11
- object.parentize(@parent, @association_name)
12
- @documents << object
13
- object.is_a?(Array) ? object.each(&:notify) : object.notify
10
+ def <<(*objects)
11
+ objects.flatten.each do |object|
12
+ object.parentize(@parent, @association_name)
13
+ @documents << object
14
+ object.notify
15
+ end
14
16
  end
15
17
 
16
18
  # Clears the association, and notifies the parents of the removal.
@@ -23,14 +25,8 @@ module Mongoid #:nodoc:
23
25
 
24
26
  # Appends the object to the +Array+, setting its parent in
25
27
  # the process.
26
- def push(object)
27
- self << object
28
- end
29
-
30
- # Appends the object to the +Array+, setting its parent in
31
- # the process.
32
- def concat(object)
33
- self << object
28
+ def concat(*objects)
29
+ self << objects
34
30
  end
35
31
 
36
32
  # Builds a new Document and adds it to the association collection. The
@@ -82,6 +78,24 @@ module Mongoid #:nodoc:
82
78
  super(@documents)
83
79
  end
84
80
 
81
+ # Used for setting associations via a nested attributes setter from the
82
+ # parent +Document+.
83
+ #
84
+ # Options:
85
+ #
86
+ # attributes: A +Hash+ of integer keys and +Hash+ values.
87
+ def nested_build(attributes)
88
+ attributes.values.each do |attrs|
89
+ build(attrs)
90
+ end
91
+ end
92
+
93
+ # Appends the object to the +Array+, setting its parent in
94
+ # the process.
95
+ def push(*objects)
96
+ self << objects
97
+ end
98
+
85
99
  class << self
86
100
 
87
101
  # Preferred method of creating a new +HasMany+ association. It will
@@ -0,0 +1,100 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Associations #:nodoc:
4
+ class HasManyRelated < DelegateClass(Array) #:nodoc:
5
+
6
+ attr_reader :klass
7
+
8
+ # Appends the object to the +Array+, setting its parent in
9
+ # the process.
10
+ def <<(*objects)
11
+ objects.flatten.each do |object|
12
+ object.send("#{@foreign_key}=", @parent.id)
13
+ @documents << object
14
+ object.save unless @parent.new_record?
15
+ end
16
+ end
17
+
18
+ # Builds a new Document and adds it to the association collection. The
19
+ # document created will be of the same class as the others in the
20
+ # association, and the attributes will be passed into the constructor.
21
+ #
22
+ # Returns the newly created object.
23
+ def build(attributes)
24
+ object = @klass.instantiate(attributes.merge(@foreign_key => @parent.id))
25
+ @documents << object
26
+ object
27
+ end
28
+
29
+ # Delegates to <<
30
+ def concat(*objects)
31
+ self << objects
32
+ end
33
+
34
+ # Creates a new Document and adds it to the association collection. The
35
+ # document created will be of the same class as the others in the
36
+ # association, and the attributes will be passed into the constructor and
37
+ # the new object will then be saved.
38
+ #
39
+ # Returns the newly created object.
40
+ def create(attributes)
41
+ object = build(attributes)
42
+ object.save
43
+ end
44
+
45
+ # Initializing a related association only requires looking up the objects
46
+ # by their ids.
47
+ #
48
+ # Options:
49
+ #
50
+ # document: The +Document+ that contains the relationship.
51
+ # options: The association +Options+.
52
+ def initialize(document, options)
53
+ @parent, @klass = document, options.klass
54
+ @foreign_key = document.class.to_s.foreign_key
55
+ @documents = @klass.all(:conditions => { @foreign_key => document.id })
56
+ super(@documents)
57
+ end
58
+
59
+ # Delegates to <<
60
+ def push(*objects)
61
+ self << objects
62
+ end
63
+
64
+ class << self
65
+ # Preferred method for creating the new +HasManyRelated+ association.
66
+ #
67
+ # Options:
68
+ #
69
+ # document: The +Document+ that contains the relationship.
70
+ # options: The association +Options+.
71
+ def instantiate(document, options)
72
+ new(document, options)
73
+ end
74
+
75
+ # Returns the macro used to create the association.
76
+ def macro
77
+ :has_many_related
78
+ end
79
+
80
+ # Perform an update of the relationship of the parent and child. This
81
+ # will assimilate the child +Document+ into the parent's object graph.
82
+ #
83
+ # Options:
84
+ #
85
+ # related: The related object
86
+ # parent: The parent +Document+ to update.
87
+ # options: The association +Options+
88
+ #
89
+ # Example:
90
+ #
91
+ # <tt>RelatesToOne.update(game, person, options)</tt>
92
+ def update(related, document, options)
93
+ name = document.class.to_s.underscore
94
+ related.each { |child| child.send("#{name}=", document) }
95
+ end
96
+ end
97
+
98
+ end
99
+ end
100
+ end