module ActiveRecord::Aggregations::ClassMethods
def composed_of(part_id, options = {}, &block)
:converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
:constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
:mapping => %w(ip to_i),
:class_name => 'IPAddr',
composed_of :ip_address,
composed_of :gps_location, :allow_nil => true
composed_of :gps_location
composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
composed_of :temperature, :mapping => %w(reading celsius)
Option examples:
in the assignment and is only called if the new value is not an instance of :class_name.
called when a new value is assigned to the value object. The converter is passed the single value that is used
* :converter - A symbol specifying the name of a class method of :class_name or a Proc that is
The default is :new.
are defined in the :mapping option, as arguments and uses them to instantiate a :class_name object.
initialize the value object. The constructor is passed all of the mapped attributes, in the order that they
* :constructor - A symbol specifying the name of the constructor method or a Proc that is called to
This defaults to +false+.
attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all mapped attributes.
* :allow_nil - Specifies that the value object will not be instantiated when all mapped
attributes are sent to the value class constructor.
name the attribute in the value object. The order in which mappings are defined determine the order in which
is represented as an array where the first item is the name of the entity attribute and the second item is the
* :mapping - Specifies the mapping of entity attributes to attributes of the value object. Each mapping
if the real class name is CompanyAddress, you'll have to specify it with this option.
from the part id. So composed_of :address will by default be linked to the Address class, but
* :class_name - Specifies the class name of the association. Use it only if that name can't be inferred
Options are:
composed_of :address adds address and address=(new_address) methods.
Adds reader and writer methods for manipulating a value object:
def composed_of(part_id, options = {}, &block) options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter) name = part_id.id2name class_name = options[:class_name] || name.camelize mapping = options[:mapping] || [ name, name ] mapping = [ mapping ] unless mapping.first.is_a?(Array) allow_nil = options[:allow_nil] || false constructor = options[:constructor] || :new converter = options[:converter] || block ActiveSupport::Deprecation.warn('The conversion block has been deprecated, use the :converter option instead.', caller) if block_given? reader_method(name, class_name, mapping, allow_nil, constructor) writer_method(name, class_name, mapping, allow_nil, converter) create_reflection(:composed_of, part_id, options, self) end
def reader_method(name, class_name, mapping, allow_nil, constructor)
def reader_method(name, class_name, mapping, allow_nil, constructor) module_eval do define_method(name) do |*args| force_reload = args.first || false if (instance_variable_get("@#{name}").nil? || force_reload) && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? }) attrs = mapping.collect {|pair| read_attribute(pair.first)} object = case constructor when Symbol class_name.constantize.send(constructor, *attrs) when Proc, Method constructor.call(*attrs) else raise ArgumentError, 'Constructor must be a symbol denoting the constructor method to call or a Proc to be invoked.' end instance_variable_set("@#{name}", object) end instance_variable_get("@#{name}") end end end
def writer_method(name, class_name, mapping, allow_nil, converter)
def writer_method(name, class_name, mapping, allow_nil, converter) module_eval do define_method("#{name}=") do |part| if part.nil? && allow_nil mapping.each { |pair| self[pair.first] = nil } instance_variable_set("@#{name}", nil) else unless part.is_a?(class_name.constantize) || converter.nil? part = case converter when Symbol class_name.constantize.send(converter, part) when Proc, Method converter.call(part) else raise ArgumentError, 'Converter must be a symbol denoting the converter method to call or a Proc to be invoked.' end end mapping.each { |pair| self[pair.first] = part.send(pair.last) } instance_variable_set("@#{name}", part.freeze) end end end end