Method: ActiveRecord::Aggregations::ClassMethods#composed_of

Defined in:
activerecord/lib/active_record/aggregations.rb

#composed_of(part_id, options = {}) ⇒ Object

Adds reader and writer methods for manipulating a value object: composed_of :address adds address and address=(new_address) methods.

Options are:

  • :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 the mapping of entity attributes to attributes of the value object. Each mapping is represented as a key-value pair where the key is the name of the entity attribute and the value is the name of the attribute in the value object. The order in which mappings are defined determines the order in which attributes are sent to the value class constructor. The mapping can be written as a hash or as an array of pairs.

  • :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 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. If :allow_nil is set to true, the converter can return nil to skip the assignment.

Option examples:

composed_of :temperature, mapping: { reading: :celsius }
composed_of :balance, class_name: "Money", mapping: { balance: :amount }
composed_of :address, mapping: { address_street: :street, address_city: :city }
composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
composed_of :gps_location
composed_of :gps_location, allow_nil: true
composed_of :ip_address,
            class_name: 'IPAddr',
            mapping: { ip: :to_i },
            constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
            converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }


225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'activerecord/lib/active_record/aggregations.rb', line 225

def composed_of(part_id, options = {})
  options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)

  unless self < Aggregations
    include Aggregations
  end

  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]

  reader_method(name, class_name, mapping, allow_nil, constructor)
  writer_method(name, class_name, mapping, allow_nil, converter)

  reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self)
  Reflection.add_aggregate_reflection self, part_id, reflection
end