mongoid 2.0.0.beta.5 → 2.0.0.beta.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/lib/mongoid.rb +10 -2
  2. data/lib/mongoid/associations.rb +82 -58
  3. data/lib/mongoid/associations/embeds_one.rb +6 -6
  4. data/lib/mongoid/associations/foreign_key.rb +35 -0
  5. data/lib/mongoid/associations/meta_data.rb +9 -0
  6. data/lib/mongoid/associations/options.rb +1 -1
  7. data/lib/mongoid/associations/proxy.rb +9 -0
  8. data/lib/mongoid/associations/{belongs_to_related.rb → referenced_in.rb} +6 -5
  9. data/lib/mongoid/associations/{has_many_related.rb → references_many.rb} +69 -26
  10. data/lib/mongoid/associations/references_many_as_array.rb +78 -0
  11. data/lib/mongoid/associations/{has_one_related.rb → references_one.rb} +16 -2
  12. data/lib/mongoid/atomicity.rb +42 -0
  13. data/lib/mongoid/attributes.rb +148 -146
  14. data/lib/mongoid/callbacks.rb +5 -1
  15. data/lib/mongoid/collections.rb +31 -1
  16. data/lib/mongoid/components.rb +4 -1
  17. data/lib/mongoid/config.rb +2 -1
  18. data/lib/mongoid/criteria.rb +9 -0
  19. data/lib/mongoid/dirty.rb +211 -212
  20. data/lib/mongoid/document.rb +126 -185
  21. data/lib/mongoid/extensions.rb +5 -0
  22. data/lib/mongoid/extensions/array/conversions.rb +3 -5
  23. data/lib/mongoid/extensions/hash/conversions.rb +19 -22
  24. data/lib/mongoid/extensions/object/conversions.rb +3 -5
  25. data/lib/mongoid/extensions/set/conversions.rb +20 -0
  26. data/lib/mongoid/field.rb +11 -0
  27. data/lib/mongoid/finders.rb +8 -0
  28. data/lib/mongoid/hierarchy.rb +76 -0
  29. data/lib/mongoid/identity.rb +37 -29
  30. data/lib/mongoid/paths.rb +46 -47
  31. data/lib/mongoid/persistence.rb +111 -113
  32. data/lib/mongoid/persistence/insert.rb +1 -1
  33. data/lib/mongoid/persistence/insert_embedded.rb +10 -5
  34. data/lib/mongoid/persistence/remove_all.rb +3 -2
  35. data/lib/mongoid/persistence/update.rb +8 -3
  36. data/lib/mongoid/railtie.rb +3 -0
  37. data/lib/mongoid/railties/database.rake +33 -18
  38. data/lib/mongoid/timestamps.rb +9 -12
  39. data/lib/mongoid/validations/uniqueness.rb +16 -4
  40. data/lib/mongoid/version.rb +1 -1
  41. data/lib/mongoid/versioning.rb +10 -11
  42. data/lib/rails/generators/mongoid/config/config_generator.rb +0 -16
  43. metadata +64 -24
@@ -0,0 +1,78 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Associations #:nodoc:
4
+ # Represents an relational one-to-many association with an object in a
5
+ # separate collection or database, stored as an array of ids on the parent
6
+ # document.
7
+ class ReferencesManyAsArray < ReferencesMany
8
+
9
+ # Append a document to this association. This will also set the appended
10
+ # document's id on the inverse association as well.
11
+ #
12
+ # Example:
13
+ #
14
+ # <tt>person.preferences << Preference.new(:name => "VGA")</tt>
15
+ def <<(*objects)
16
+ @target = @target.entries
17
+ objects.flatten.each do |object|
18
+ # First set the documents id on the parent array of ids.
19
+ @parent.send(@foreign_key) << object.id
20
+ # Then we need to set the parent's id on the documents array of ids
21
+ # to get the inverse side of the association as well. Note, need a
22
+ # clean way to handle this with new documents - we want to set the
23
+ # actual objects as well, but dont want to get in an infinite loop
24
+ # while doing so.
25
+ object.send(reverse_key(object)) << @parent.id
26
+ @target << object
27
+ end
28
+ end
29
+
30
+ alias :concat :<<
31
+ alias :push :<<
32
+
33
+ # Builds a new Document and adds it to the association collection. The
34
+ # document created will be of the same class as the others in the
35
+ # association, and the attributes will be passed into the constructor.
36
+ #
37
+ # Returns the newly created object.
38
+ def build(attributes = nil)
39
+ load_target
40
+ document = @klass.instantiate(attributes || {})
41
+ push(document); document
42
+ end
43
+
44
+ protected
45
+ # Find the inverse key for the supplied document.
46
+ def reverse_key(document)
47
+ document.send(@options.inverse_of).options.foreign_key
48
+ end
49
+
50
+ # The default query used for retrieving the documents from the database.
51
+ def query
52
+ @query ||= lambda { @klass.any_in(:_id => @parent.send(@foreign_key)) }
53
+ end
54
+
55
+ class << self
56
+ # Perform an update of the relationship of the parent and child. This
57
+ # will assimilate the child +Document+ into the parent's object graph.
58
+ #
59
+ # Options:
60
+ #
61
+ # related: The related object
62
+ # parent: The parent +Document+ to update.
63
+ # options: The association +Options+
64
+ #
65
+ # Example:
66
+ #
67
+ # <tt>RelatesToManyAsArray.update(preferences, person, options)</tt>
68
+ def update(target, document, options)
69
+ target.each do |child|
70
+ name = child.associations[options.inverse_of.to_s].options.name
71
+ child.send(name) << document
72
+ end
73
+ instantiate(document, options, target)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -3,7 +3,7 @@ module Mongoid #:nodoc:
3
3
  module Associations #:nodoc:
4
4
  # Represents an relational one-to-one association with an object in a
5
5
  # separate collection or database.
6
- class HasOneRelated < Proxy
6
+ class ReferencesOne < Proxy
7
7
 
8
8
  delegate :nil?, :to => :target
9
9
 
@@ -42,6 +42,20 @@ module Mongoid #:nodoc:
42
42
  extends(options)
43
43
  end
44
44
 
45
+ # Used for setting the association via a nested attributes setter on the
46
+ # parent +Document+. Called when using accepts_nested_attributes_for.
47
+ #
48
+ # Options:
49
+ #
50
+ # attributes: The attributes for the new association
51
+ #
52
+ # Returns:
53
+ #
54
+ # A new target document.
55
+ def nested_build(attributes, options = nil)
56
+ build(attributes) unless @target.blank? && options[:update_only]
57
+ end
58
+
45
59
  class << self
46
60
  # Preferred method for creating the new +RelatesToMany+ association.
47
61
  #
@@ -55,7 +69,7 @@ module Mongoid #:nodoc:
55
69
 
56
70
  # Returns the macro used to create the association.
57
71
  def macro
58
- :has_one_related
72
+ :references_one
59
73
  end
60
74
 
61
75
  # Perform an update of the relationship of the parent and child. This
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Atomicity #:nodoc:
4
+ extend ActiveSupport::Concern
5
+
6
+ # Get all the atomic updates that need to happen for the current
7
+ # +Document+. This will include all changes that need to happen in the
8
+ # entire hierarchy that exists below where the save call was made.
9
+ #
10
+ # Example:
11
+ #
12
+ # <tt>person.save</tt> # Saves entire tree
13
+ #
14
+ # Returns:
15
+ #
16
+ # A +Hash+ of all atomic updates that need to occur.
17
+ def _updates
18
+ _children.inject({ "$set" => _sets, "$push" => {}}) do |updates, child|
19
+ updates["$set"].update(child._sets)
20
+ updates["$push"].update(child._pushes)
21
+ updates
22
+ end.delete_if do |key, value|
23
+ value.empty?
24
+ end
25
+ end
26
+
27
+ protected
28
+ # Get all the push attributes that need to occur.
29
+ def _pushes
30
+ (new_record? && embedded_many? && !_parent.new_record?) ? { _path => raw_attributes } : {}
31
+ end
32
+
33
+ # Get all the attributes that need to be set.
34
+ def _sets
35
+ if changed? && !new_record?
36
+ setters
37
+ else
38
+ embedded_one? && new_record? ? { _path => raw_attributes } : {}
39
+ end
40
+ end
41
+ end
42
+ end
@@ -2,172 +2,174 @@
2
2
  module Mongoid #:nodoc:
3
3
  module Attributes
4
4
  extend ActiveSupport::Concern
5
- module InstanceMethods
6
- # Get the id associated with this object. This will pull the _id value out
7
- # of the attributes +Hash+.
8
- def id
9
- @attributes["_id"]
10
- end
11
5
 
12
- # Set the id of the +Document+ to a new one.
13
- def id=(new_id)
14
- @attributes["_id"] = new_id
15
- end
6
+ # Get the id associated with this object. This will pull the _id value out
7
+ # of the attributes +Hash+.
8
+ def id
9
+ @attributes["_id"]
10
+ end
16
11
 
17
- alias :_id :id
18
- alias :_id= :id=
19
-
20
- # Used for allowing accessor methods for dynamic attributes.
21
- def method_missing(name, *args)
22
- attr = name.to_s
23
- return super unless @attributes.has_key?(attr.reader)
24
- if attr.writer?
25
- # "args.size > 1" allows to simulate 1.8 behavior of "*args"
26
- @attributes[attr.reader] = (args.size > 1) ? args : args.first
27
- else
28
- @attributes[attr.reader]
29
- end
12
+ # Set the id of the +Document+ to a new one.
13
+ def id=(new_id)
14
+ @attributes["_id"] = new_id
15
+ end
16
+
17
+ alias :_id :id
18
+ alias :_id= :id=
19
+
20
+ # Used for allowing accessor methods for dynamic attributes.
21
+ def method_missing(name, *args)
22
+ attr = name.to_s
23
+ return super unless @attributes.has_key?(attr.reader)
24
+ if attr.writer?
25
+ # "args.size > 1" allows to simulate 1.8 behavior of "*args"
26
+ write_attribute(attr.reader, (args.size > 1) ? args : args.first)
27
+ else
28
+ read_attribute(attr.reader)
30
29
  end
30
+ end
31
31
 
32
- # Process the provided attributes casting them to their proper values if a
33
- # field exists for them on the +Document+. This will be limited to only the
34
- # attributes provided in the suppied +Hash+ so that no extra nil values get
35
- # put into the document's attributes.
36
- def process(attrs = nil)
37
- (attrs || {}).each_pair do |key, value|
38
- if set_allowed?(key)
39
- @attributes[key.to_s] = value
40
- elsif write_allowed?(key)
41
- send("#{key}=", value)
42
- end
32
+ # Process the provided attributes casting them to their proper values if a
33
+ # field exists for them on the +Document+. This will be limited to only the
34
+ # attributes provided in the suppied +Hash+ so that no extra nil values get
35
+ # put into the document's attributes.
36
+ def process(attrs = nil)
37
+ (attrs || {}).each_pair do |key, value|
38
+ if set_allowed?(key)
39
+ write_attribute(key, value)
40
+ elsif write_allowed?(key)
41
+ send("#{key}=", value)
43
42
  end
44
- setup_modifications
45
43
  end
44
+ setup_modifications
45
+ end
46
46
 
47
- # Read a value from the +Document+ attributes. If the value does not exist
48
- # it will return nil.
49
- #
50
- # Options:
51
- #
52
- # name: The name of the attribute to get.
53
- #
54
- # Example:
55
- #
56
- # <tt>person.read_attribute(:title)</tt>
57
- def read_attribute(name)
58
- access = name.to_s
59
- accessed(access, fields[access].get(@attributes[access]))
60
- end
47
+ # Read a value from the +Document+ attributes. If the value does not exist
48
+ # it will return nil.
49
+ #
50
+ # Options:
51
+ #
52
+ # name: The name of the attribute to get.
53
+ #
54
+ # Example:
55
+ #
56
+ # <tt>person.read_attribute(:title)</tt>
57
+ def read_attribute(name)
58
+ access = name.to_s
59
+ value = @attributes[access]
60
+ typed_value = fields.has_key?(access) ? fields[access].get(value) : value
61
+ accessed(access, typed_value)
62
+ end
61
63
 
62
- # Remove a value from the +Document+ attributes. If the value does not exist
63
- # it will fail gracefully.
64
- #
65
- # Options:
66
- #
67
- # name: The name of the attribute to remove.
68
- #
69
- # Example:
70
- #
71
- # <tt>person.remove_attribute(:title)</tt>
72
- def remove_attribute(name)
73
- access = name.to_s
74
- modify(access, @attributes.delete(name.to_s), nil)
75
- end
64
+ # Remove a value from the +Document+ attributes. If the value does not exist
65
+ # it will fail gracefully.
66
+ #
67
+ # Options:
68
+ #
69
+ # name: The name of the attribute to remove.
70
+ #
71
+ # Example:
72
+ #
73
+ # <tt>person.remove_attribute(:title)</tt>
74
+ def remove_attribute(name)
75
+ access = name.to_s
76
+ modify(access, @attributes.delete(name.to_s), nil)
77
+ end
76
78
 
77
- # Returns the object type. This corresponds to the name of the class that
78
- # this +Document+ is, which is used in determining the class to
79
- # instantiate in various cases.
80
- def _type
81
- @attributes["_type"]
82
- end
79
+ # Returns the object type. This corresponds to the name of the class that
80
+ # this +Document+ is, which is used in determining the class to
81
+ # instantiate in various cases.
82
+ def _type
83
+ @attributes["_type"]
84
+ end
83
85
 
84
- # Set the type of the +Document+. This should be the name of the class.
85
- def _type=(new_type)
86
- @attributes["_type"] = new_type
87
- end
86
+ # Set the type of the +Document+. This should be the name of the class.
87
+ def _type=(new_type)
88
+ @attributes["_type"] = new_type
89
+ end
88
90
 
89
- # Write a single attribute to the +Document+ attribute +Hash+. This will
90
- # also fire the before and after update callbacks, and perform any
91
- # necessary typecasting.
92
- #
93
- # Options:
94
- #
95
- # name: The name of the attribute to update.
96
- # value: The value to set for the attribute.
97
- #
98
- # Example:
99
- #
100
- # <tt>person.write_attribute(:title, "Mr.")</tt>
101
- #
102
- # This will also cause the observing +Document+ to notify it's parent if
103
- # there is any.
104
- def write_attribute(name, value)
105
- access = name.to_s
106
- modify(access, @attributes[access], fields[access].set(value))
107
- notify if !id.blank? && new_record?
108
- end
91
+ # Write a single attribute to the +Document+ attribute +Hash+. This will
92
+ # also fire the before and after update callbacks, and perform any
93
+ # necessary typecasting.
94
+ #
95
+ # Options:
96
+ #
97
+ # name: The name of the attribute to update.
98
+ # value: The value to set for the attribute.
99
+ #
100
+ # Example:
101
+ #
102
+ # <tt>person.write_attribute(:title, "Mr.")</tt>
103
+ #
104
+ # This will also cause the observing +Document+ to notify it's parent if
105
+ # there is any.
106
+ def write_attribute(name, value)
107
+ access = name.to_s
108
+ typed_value = fields.has_key?(access) ? fields[access].set(value) : value
109
+ modify(access, @attributes[access], typed_value)
110
+ notify if !id.blank? && new_record?
111
+ end
109
112
 
110
- # Writes the supplied attributes +Hash+ to the +Document+. This will only
111
- # overwrite existing attributes if they are present in the new +Hash+, all
112
- # others will be preserved.
113
- #
114
- # Options:
115
- #
116
- # attrs: The +Hash+ of new attributes to set on the +Document+
117
- #
118
- # Example:
119
- #
120
- # <tt>person.write_attributes(:title => "Mr.")</tt>
121
- #
122
- # This will also cause the observing +Document+ to notify it's parent if
123
- # there is any.
124
- def write_attributes(attrs = nil)
125
- process(attrs || {})
126
- identified = !id.blank?
127
- if new_record? && !identified
128
- identify; notify
129
- end
130
- end
131
- alias :attributes= :write_attributes
132
-
133
- protected
134
- # apply default values to attributes - calling procs as required
135
- def default_attributes
136
- default_values = defaults
137
- default_values.each_pair do |key, val|
138
- default_values[key] = val.call if val.respond_to?(:call)
139
- end
140
- default_values || {}
113
+ # Writes the supplied attributes +Hash+ to the +Document+. This will only
114
+ # overwrite existing attributes if they are present in the new +Hash+, all
115
+ # others will be preserved.
116
+ #
117
+ # Options:
118
+ #
119
+ # attrs: The +Hash+ of new attributes to set on the +Document+
120
+ #
121
+ # Example:
122
+ #
123
+ # <tt>person.write_attributes(:title => "Mr.")</tt>
124
+ #
125
+ # This will also cause the observing +Document+ to notify it's parent if
126
+ # there is any.
127
+ def write_attributes(attrs = nil)
128
+ process(attrs || {})
129
+ identified = !id.blank?
130
+ if new_record? && !identified
131
+ identify; notify
141
132
  end
133
+ end
134
+ alias :attributes= :write_attributes
142
135
 
143
- # Return true if dynamic field setting is enabled.
144
- def set_allowed?(key)
145
- Mongoid.allow_dynamic_fields && !respond_to?("#{key}=")
136
+ protected
137
+ # apply default values to attributes - calling procs as required
138
+ def default_attributes
139
+ default_values = defaults
140
+ default_values.each_pair do |key, val|
141
+ default_values[key] = val.call if val.respond_to?(:call)
146
142
  end
143
+ default_values || {}
144
+ end
147
145
 
148
- # Used when supplying a :reject_if block as an option to
149
- # accepts_nested_attributes_for
150
- def reject(attributes, options)
151
- rejector = options[:reject_if]
152
- if rejector
153
- attributes.delete_if do |key, value|
154
- rejector.call(value)
155
- end
146
+ # Return true if dynamic field setting is enabled.
147
+ def set_allowed?(key)
148
+ Mongoid.allow_dynamic_fields && !respond_to?("#{key}=")
149
+ end
150
+
151
+ # Used when supplying a :reject_if block as an option to
152
+ # accepts_nested_attributes_for
153
+ def reject(attributes, options)
154
+ rejector = options[:reject_if]
155
+ if rejector
156
+ attributes.delete_if do |key, value|
157
+ rejector.call(value)
156
158
  end
157
159
  end
160
+ end
158
161
 
159
- # Used when supplying a :limit as an option to accepts_nested_attributes_for
160
- def limit(attributes, name, options)
161
- raise Mongoid::Errors::TooManyNestedAttributeRecords.new(name, options[:limit]) if options[:limit] && attributes.size > options[:limit]
162
- end
162
+ # Used when supplying a :limit as an option to accepts_nested_attributes_for
163
+ def limit(attributes, name, options)
164
+ raise Mongoid::Errors::TooManyNestedAttributeRecords.new(name, options[:limit]) if options[:limit] && attributes.size > options[:limit]
165
+ end
163
166
 
164
- # Return true if writing to the given field is allowed
165
- def write_allowed?(key)
166
- name = key.to_s
167
- existing = fields[name]
168
- return true unless existing
169
- existing.accessible?
170
- end
167
+ # Return true if writing to the given field is allowed
168
+ def write_allowed?(key)
169
+ name = key.to_s
170
+ existing = fields[name]
171
+ return true unless existing
172
+ existing.accessible?
171
173
  end
172
174
 
173
175
  module ClassMethods