module Rails::DataMapper::MultiparameterAttributes

Include this module into a DataMapper model to enable multiparameter attributes.

A multiparameter attribute has +attr(Xc)+ as name where attr specifies the attribute, X the position of the value, and c an optional typecast identifier. All values that share an attr are sorted by their position, optionally cast using #to_c (where c is the typecast identifier) and then used to instantiate the proper attribute value.

@example

# Assigning a hash with multiparameter values for a +Date+ property called
# +written_on+:
resource.attributes = {
    'written_on(1i)' => '2004',
    'written_on(2i)' => '6',
    'written_on(3i)' => '24' }

# +Date+ will be initialized with each string cast to a number using
# #to_i.
resource.written_on == Date.new(2004, 6, 24)

Public Instance Methods

attributes=(attributes) click to toggle source

Merges multiparameter attributes and calls super with the merged attributes.

@param [Hash{String,Symbol=>Object}] attributes

Names and values of attributes to assign.

@return [Hash]

Names and values of attributes assigned.

@raise [MultiparameterAssignmentErrors]

One or more multiparameters could not be assigned.

@api public

Calls superclass method
# File lib/dm-rails/multiparameter_attributes.rb, line 82
def attributes=(attributes)
  attribs = attributes.dup
  multi_parameter_attributes = []
  attribs.each do |k, v|
    if k.to_s.include?("(")
      multi_parameter_attributes << [ k, v ]
      attribs.delete(k)
    end
  end

  attribs.merge!(merge_multiparameter_attributes(multi_parameter_attributes))
  super(attribs)
end

Protected Instance Methods

extract_multiparameter_attributes(pairs) click to toggle source
# File lib/dm-rails/multiparameter_attributes.rb, line 136
def extract_multiparameter_attributes(pairs)
  attributes = {}

  for multiparameter_name, value in pairs
    unless multiparameter_name =~ /\A ([^\)]+) \(  ([0-9]+) ([a-z])?  \) \z/x
      raise "Invalid multiparameter name #{multiparameter_name.inspect}."
    end

    name, position, typecast = $1, $2, $3
    attributes[name] ||= []

    parameter_value =
      if value.empty?
        nil
      elsif typecast
        value.send('to_' + typecast)
      else
        value
      end

    attributes[name] << [ position, parameter_value ]
  end

  # Order each parameter array according to the position, then discard the
  # position.
  attributes.each { |name, values|
    attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last }
  }
end
merge_multiparameter_attributes(pairs) click to toggle source
# File lib/dm-rails/multiparameter_attributes.rb, line 97
def merge_multiparameter_attributes(pairs)
  pairs = extract_multiparameter_attributes(pairs)

  errors = []
  attributes = {}
  pairs.each do |name, values_with_empty_parameters|
    # ActiveRecord keeps the empty values to set dates without a year.
    # Removing all nils (instead of removing only trailing nils) seems
    # like a weird behavior though.
    values = values_with_empty_parameters.compact

    if values.empty?
      attributes[name] = nil
      next
    end

    klass = properties[name].primitive
    begin
      attributes[name] =
        if klass == Time
          Time.local(*values)
        elsif klass == Date
          # Date does not replace nil values with defaults.
          Date.new(*values_with_empty_parameters.map { |v| v.nil? ? 1 : v })
        else
          klass.new(*values)
        end
    rescue => ex
      errors << MultiparameterAssignmentError.new(name, values, ex)
    end
  end

  unless errors.empty?
    raise MultiparameterAssignmentErrors.new(errors)
  end

  attributes
end