module ActiveRecord::AttributeAssignment

Public Instance Methods

assign_attributes(new_attributes, options = {}) click to toggle source

Allows you to set all the attributes for a particular mass-assignment security role by passing in a hash of attributes with keys matching the attribute names (which again matches the column names) and the role name using the :as option.

To bypass mass-assignment security you can use the :without_protection => true option.

class User < ActiveRecord::Base
  attr_accessible :name
  attr_accessible :name, :is_admin, :as => :admin
end

user = User.new
user.assign_attributes({ :name => 'Josh', :is_admin => true })
user.name       # => "Josh"
user.is_admin?  # => false

user = User.new
user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
user.name       # => "Josh"
user.is_admin?  # => true

user = User.new
user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
user.name       # => "Josh"
user.is_admin?  # => true
# File lib/active_record/attribute_assignment.rb, line 66
def assign_attributes(new_attributes, options = {})
  return if new_attributes.blank?

  attributes = new_attributes.stringify_keys
  multi_parameter_attributes = []
  nested_parameter_attributes = []
  @mass_assignment_options = options

  unless options[:without_protection]
    attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
  end

  attributes.each do |k, v|
    if k.include?("(")
      multi_parameter_attributes << [ k, v ]
    elsif respond_to?("#{k}=")
      if v.is_a?(Hash)
        nested_parameter_attributes << [ k, v ]
      else
        send("#{k}=", v)
      end
    else
      raise(UnknownAttributeError, "unknown attribute: #{k}")
    end
  end

  # assign any deferred nested attributes after the base attributes have been set
  nested_parameter_attributes.each do |k,v|
    send("#{k}=", v)
  end

  @mass_assignment_options = nil
  assign_multiparameter_attributes(multi_parameter_attributes)
end
attributes=(new_attributes) click to toggle source

Allows you to set all the attributes at once by passing in a hash with keys matching the attribute names (which again matches the column names).

If any attributes are protected by either attr_protected or attr_accessible then only settable attributes will be assigned.

class User < ActiveRecord::Base
  attr_protected :is_admin
end

user = User.new
user.attributes = { :username => 'Phusion', :is_admin => true }
user.username   # => "Phusion"
user.is_admin?  # => false
# File lib/active_record/attribute_assignment.rb, line 33
def attributes=(new_attributes)
  return unless new_attributes.is_a?(Hash)

  assign_attributes(new_attributes)
end

Protected Instance Methods

mass_assignment_options() click to toggle source
# File lib/active_record/attribute_assignment.rb, line 103
def mass_assignment_options
  @mass_assignment_options ||= {}
end
mass_assignment_role() click to toggle source
# File lib/active_record/attribute_assignment.rb, line 107
def mass_assignment_role
  mass_assignment_options[:as] || :default
end

Private Instance Methods

assign_multiparameter_attributes(pairs) click to toggle source

Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done by calling new on the column type or aggregation type (through composed_of) object with these parameters. So having the pairs written_on(1) = “2004”, written_on(2) = “6”, written_on(3) = “24”, will instantiate written_on (a date type) with Date.new(“2004”, “6”, “24”). You can also specify a typecast character in the parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the attribute will be set to nil.

# File lib/active_record/attribute_assignment.rb, line 120
def assign_multiparameter_attributes(pairs)
  execute_callstack_for_multiparameter_attributes(
    extract_callstack_for_multiparameter_attributes(pairs)
  )
end
execute_callstack_for_multiparameter_attributes(callstack) click to toggle source
# File lib/active_record/attribute_assignment.rb, line 134
def execute_callstack_for_multiparameter_attributes(callstack)
  errors = []
  callstack.each do |name, values_with_empty_parameters|
    begin
      send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
    rescue => ex
      errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
    end
  end
  unless errors.empty?
    raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
  end
end
extract_callstack_for_multiparameter_attributes(pairs) click to toggle source
# File lib/active_record/attribute_assignment.rb, line 197
def extract_callstack_for_multiparameter_attributes(pairs)
  attributes = { }

  pairs.each do |pair|
    multiparameter_name, value = pair
    attribute_name = multiparameter_name.split("(").first
    attributes[attribute_name] = {} unless attributes.include?(attribute_name)

    parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
    attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
  end

  attributes
end
extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100) click to toggle source
# File lib/active_record/attribute_assignment.rb, line 193
def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
  [values_hash_from_param.keys.max,upper_cap].min
end
find_parameter_position(multiparameter_name) click to toggle source
# File lib/active_record/attribute_assignment.rb, line 216
def find_parameter_position(multiparameter_name)
  multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
end
instantiate_time_object(name, values) click to toggle source
# File lib/active_record/attribute_assignment.rb, line 126
def instantiate_time_object(name, values)
  if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
    Time.zone.local(*values)
  else
    Time.time_with_datetime_fallback(self.class.default_timezone, *values)
  end
end
read_date_parameter_value(name, values_hash_from_param) click to toggle source
# File lib/active_record/attribute_assignment.rb, line 174
def read_date_parameter_value(name, values_hash_from_param)
  return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
  set_values = [values_hash_from_param[1], values_hash_from_param[2], values_hash_from_param[3]]
  begin
    Date.new(*set_values)
  rescue ArgumentError # if Date.new raises an exception on an invalid date
    instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
  end
end
read_other_parameter_value(klass, name, values_hash_from_param) click to toggle source
# File lib/active_record/attribute_assignment.rb, line 184
def read_other_parameter_value(klass, name, values_hash_from_param)
  max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
  values = (1..max_position).collect do |position|
    raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
    values_hash_from_param[position]
  end
  klass.new(*values)
end
read_time_parameter_value(name, values_hash_from_param) click to toggle source
# File lib/active_record/attribute_assignment.rb, line 161
def read_time_parameter_value(name, values_hash_from_param)
  # If Date bits were not provided, error
  raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
  max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
  # If Date bits were provided but blank, then return nil
  return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}

  set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
  # If Time bits are not there, then default to 0
  (3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]}
  instantiate_time_object(name, set_values)
end
read_value_from_parameter(name, values_hash_from_param) click to toggle source
# File lib/active_record/attribute_assignment.rb, line 148
def read_value_from_parameter(name, values_hash_from_param)
  klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
  if values_hash_from_param.values.all?{|v|v.nil?}
    nil
  elsif klass == Time
    read_time_parameter_value(name, values_hash_from_param)
  elsif klass == Date
    read_date_parameter_value(name, values_hash_from_param)
  else
    read_other_parameter_value(klass, name, values_hash_from_param)
  end
end
type_cast_attribute_value(multiparameter_name, value) click to toggle source
# File lib/active_record/attribute_assignment.rb, line 212
def type_cast_attribute_value(multiparameter_name, value)
  multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
end