From b518b6c0d3e7796e303c2396de97a8d901aeb308 Mon Sep 17 00:00:00 2001 From: Rob Anderton Date: Wed, 10 Sep 2008 17:04:55 +0100 Subject: Expanded documentation for new composed_of options Signed-off-by: Michael Koziarski [#892 state:committed] --- activerecord/lib/active_record/aggregations.rb | 63 ++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 10 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index f3d73591e5..9eee7f2d98 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -109,6 +109,45 @@ module ActiveRecord # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects # immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable # + # == Custom constructors and converters + # + # By default value objects are initialized by calling the new constructor of the value class passing each of the + # mapped attributes, in the order specified by the :mapping option, as arguments. If the value class doesn't support + # this convention then +composed_of+ allows a custom constructor to be specified. + # + # When a new value is assigned to the value object the default assumption is that the new value is an instance of the value + # class. Specifying a custom converter allows the new value to be automatically converted to an instance of value class if + # necessary. + # + # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be aggregated using the + # NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor for the value class is called +create+ and it + # expects a CIDR address string as a parameter. New values can be assigned to the value object using either another + # NetAddr::CIDR object, a string or an array. The :constructor and :converter options can be used to + # meet these requirements: + # + # class NetworkResource < ActiveRecord::Base + # composed_of :cidr, + # :class_name => 'NetAddr::CIDR', + # :mapping => [ %w(network_address network), %w(cidr_range bits) ], + # :allow_nil => true, + # :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") }, + # :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) } + # end + # + # # This calls the :constructor + # network_resource = NetworkResource.new(:network_address => '192.168.0.1', :cidr_range => 24) + # + # # These assignments will both use the :converter + # network_resource.cidr = [ '192.168.2.1', 8 ] + # network_resource.cidr = '192.168.0.1/24' + # + # # This assignment won't use the :converter as the value is already an instance of the value class + # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8') + # + # # Saving and then reloading will use the :constructor on reload + # network_resource.save + # network_resource.reload + # # == Finding records by a value object # # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database by specifying an instance @@ -122,19 +161,23 @@ module ActiveRecord # composed_of :address adds address and address=(new_address) methods. # # Options are: - # * :class_name - specify the class name of the association. Use it only if that name can't be inferred + # * :class_name - Specifies the class name of the association. Use it only if that name can't be inferred # from the part id. So composed_of :address will by default be linked to the Address class, but # if the real class name is CompanyAddress, you'll have to specify it with this option. - # * :mapping - specifies a number of mapping arrays (attribute, parameter) that bind an attribute name - # to a constructor parameter on the value class. - # * :allow_nil - specifies that the aggregate object will not be instantiated when all mapped - # attributes are +nil+. Setting the aggregate class to +nil+ has the effect of writing +nil+ to all mapped attributes. + # * :mapping - Specifies the mapping of entity attributes to attributes of the value object. Each mapping + # is represented as an array where the first item is the name of the entity attribute and the second item is the + # name the attribute in the value object. The order in which mappings are defined determine the order in which + # attributes are sent to the value class constructor. + # * :allow_nil - Specifies that the value object will not be instantiated when all mapped + # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all mapped attributes. # This defaults to +false+. - # * :constructor - a symbol specifying the name of the constructor method or a Proc that will be used to convert the - # attributes that are mapped to the aggregation to instantiate a :class_name object. The default is +:new+. - # * :converter - a symbol specifying the name of a class method of :class_name or a Proc that will be used to convert - # the argument that is passed to the writer method into an instance of :class_name. The converter will only be called - # if the argument is not already an instance of :class_name. + # * :constructor - A symbol specifying the name of the constructor method or a Proc that is called to + # initialize the value object. The constructor is passed all of the mapped attributes, in the order that they + # are defined in the :mapping option, as arguments and uses them to instantiate a :class_name object. + # The default is :new. + # * :converter - A symbol specifying the name of a class method of :class_name or a Proc that is + # called when a new value is assigned to the value object. The converter is passed the single value that is used + # in the assignment and is only called if the new value is not an instance of :class_name. # # Option examples: # composed_of :temperature, :mapping => %w(reading celsius) -- cgit v1.2.3