max
max

Reputation: 167

How to assign multiple attributes at once to an object in Ruby

I have an object Foo and want to assign multiple attributes to it at once, similar to assign_attributes in Rails:

class Foo
    attr_accessor :a, :b, :c
end

f = Foo.new
my_hash = {a: "foo", b: "bar", c: "baz"}
f.assign_attributes(my_hash)

The above does not work except if the class is an ActiveRecord Model in Rails. Is there any way to do it in Ruby?

Upvotes: 2

Views: 1452

Answers (2)

Stefan
Stefan

Reputation: 114208

You can implement the mass-assignment method yourself.

One option is to set the corresponding instance variables via instance_variable_set:

class Foo
  attr_accessor :a, :b, :c

  def assign_attributes(attrs)
    attrs.each_pair do |attr, value|
      instance_variable_set("@#{attr}", value)
    end
  end
end

Note that this will by-pass any custom setters. As said in the docs:

This may circumvent the encapsulation intended by the author of the class, so it should be used with care.

Another way is to dynamically invoke the setters via public_send:

  def assign_attributes(attrs)
    attrs.each_pair do |attr, value|
      public_send("#{attr}=", value)
    end
  end

This is equivalent to setting each single attribute sequentially. If a setter has been (re)defined to include constraints and controls on the value being set, the latter approach respects that.

It also raises an exception if you try to set undefined attributes: (because the corresponding setter doesn't exist)

f = Foo.new
f.assign_attributes(d: 'qux')
#=> NoMehodError: undefined method `d=' for #<Foo:0x00007fbb76038430>

In addition, you might want to ensure that the passed argument is indeed a hash and maybe raise a custom exception if the provided attributes are invalid / unknown.

Upvotes: 7

Nitin Srivastava
Nitin Srivastava

Reputation: 1424

The assign_attributes is an instance method of ActiveRecord

You have to define your assign_attributes method if not using ActiveRecord

def assign_attributes(attrs_hash)
  attrs_hash.each do |k, v|
    self.instance_variable_set("@#{k}", v)
  end
end

Upvotes: 0

Related Questions