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

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