public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Expanded documentation for new composed_of options

Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#892 state:committed]
Rob Anderton (author)
Wed Sep 10 09:04:55 -0700 2008
NZKoz (committer)
Wed Sep 10 09:28:57 -0700 2008
commit  b518b6c0d3e7796e303c2396de97a8d901aeb308
tree    cc672d917c93ef232fa347cbdc220535d97aaffc
parent  2cee51d5c1d143f6fe0096ba6cbd1db1ecbe2d90
...
109
110
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
113
114
...
122
123
124
125
 
126
127
128
129
130
131
 
 
 
 
 
 
132
133
134
135
136
137
 
 
 
 
 
 
 
138
139
140
...
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
...
161
162
163
 
164
165
166
 
 
 
 
167
168
169
170
171
172
173
 
 
 
 
 
174
175
176
177
178
179
180
181
182
183
0
@@ -109,6 +109,45 @@ module ActiveRecord
0
     # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
0
     # immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
0
     #
0
+    # == Custom constructors and converters
0
+    #
0
+    # By default value objects are initialized by calling the <tt>new</tt> constructor of the value class passing each of the
0
+    # mapped attributes, in the order specified by the <tt>:mapping</tt> option, as arguments. If the value class doesn't support
0
+    # this convention then +composed_of+ allows a custom constructor to be specified.
0
+    #
0
+    # When a new value is assigned to the value object the default assumption is that the new value is an instance of the value
0
+    # class. Specifying a custom converter allows the new value to be automatically converted to an instance of value class if
0
+    # necessary.
0
+    #
0
+    # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be aggregated using the
0
+    # NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor for the value class is called +create+ and it
0
+    # expects a CIDR address string as a parameter. New values can be assigned to the value object using either another
0
+    # NetAddr::CIDR object, a string or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to
0
+    # meet these requirements:
0
+    #
0
+    #   class NetworkResource < ActiveRecord::Base
0
+    #     composed_of :cidr,
0
+    #                 :class_name => 'NetAddr::CIDR',
0
+    #                 :mapping => [ %w(network_address network), %w(cidr_range bits) ],
0
+    #                 :allow_nil => true,
0
+    #                 :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
0
+    #                 :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
0
+    #   end
0
+    #
0
+    #   # This calls the :constructor
0
+    #   network_resource = NetworkResource.new(:network_address => '192.168.0.1', :cidr_range => 24)
0
+    #
0
+    #   # These assignments will both use the :converter
0
+    #   network_resource.cidr = [ '192.168.2.1', 8 ]
0
+    #   network_resource.cidr = '192.168.0.1/24'
0
+    #
0
+    #   # This assignment won't use the :converter as the value is already an instance of the value class
0
+    #   network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
0
+    #
0
+    #   # Saving and then reloading will use the :constructor on reload
0
+    #   network_resource.save
0
+    #   network_resource.reload
0
+    #
0
     # == Finding records by a value object
0
     #
0
     # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database by specifying an instance
0
@@ -122,19 +161,23 @@ module ActiveRecord
0
       # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
0
       #
0
       # Options are:
0
-      # * <tt>:class_name</tt>  - specify the class name of the association. Use it only if that name can't be inferred
0
+      # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name can't be inferred
0
       #   from the part id. So <tt>composed_of :address</tt> will by default be linked to the Address class, but
0
       #   if the real class name is CompanyAddress, you'll have to specify it with this option.
0
-      # * <tt>:mapping</tt> - specifies a number of mapping arrays (attribute, parameter) that bind an attribute name
0
-      #   to a constructor parameter on the value class.
0
-      # * <tt>:allow_nil</tt> - specifies that the aggregate object will not be instantiated when all mapped
0
-      #   attributes are +nil+.  Setting the aggregate class to +nil+ has the effect of writing +nil+ to all mapped attributes.
0
+      # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value object. Each mapping
0
+      #   is represented as an array where the first item is the name of the entity attribute and the second item is the
0
+      #   name the attribute in the value object. The order in which mappings are defined determine the order in which
0
+      #   attributes are sent to the value class constructor.
0
+      # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
0
+      #   attributes are +nil+.  Setting the value object to +nil+ has the effect of writing +nil+ to all mapped attributes.
0
       #   This defaults to +false+.
0
-      # * <tt>:constructor</tt> - a symbol specifying the name of the constructor method or a Proc that will be used to convert the
0
-      #   attributes that are mapped to the aggregation to instantiate a <tt>:class_name</tt> object. The default is +:new+.
0
-      # * <tt>:converter</tt> - a symbol specifying the name of a class method of <tt>:class_name</tt> or a Proc that will be used to convert
0
-      #   the argument that is passed to the writer method into an instance of <tt>:class_name</tt>. The converter will only be called
0
-      #   if the argument is not already an instance of <tt>:class_name</tt>.
0
+      # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that is called to
0
+      #   initialize the value object. The constructor is passed all of the mapped attributes, in the order that they
0
+      #   are defined in the <tt>:mapping option</tt>, as arguments and uses them to instantiate a <tt>:class_name</tt> object.
0
+      #   The default is <tt>:new</tt>.
0
+      # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt> or a Proc that is
0
+      #   called when a new value is assigned to the value object. The converter is passed the single value that is used
0
+      #   in the assignment and is only called if the new value is not an instance of <tt>:class_name</tt>.
0
       #
0
       # Option examples:
0
       #   composed_of :temperature, :mapping => %w(reading celsius)

Comments