53

How can I set an object attribute dynamically in Ruby e.g.

def set_property(obj, prop_name, prop_value)
    #need to do something like > obj.prop_name = prop_value 

    #we can use eval but I'll prefer a faster/cleaner alternative:
    eval "obj.#{prop_name} = #{prop_value}"
end
neebz
  • 11,465
  • 7
  • 47
  • 64

4 Answers4

97

Use send:

def set_property(obj, prop_name, prop_value)
    obj.send("#{prop_name}=",prop_value)
end
lucapette
  • 20,564
  • 6
  • 65
  • 59
  • 2
    If you are use 1.9.2 you should call public_send instead of send as send can call private methods of an object as well see [http://www.joshstaiger.org/archives/2006/12/the_ruby_send_h.html] – Moiz Raja Oct 21 '11 at 12:55
  • 20
    This does not work for setting a property _dynamically_. Doing `obj.send("foo=", "bar")` will break unless the `foo=` method is already defined (by an attribute accessor or explicit method definition). All `send` does is invoke an existing method using the specified arguments. See: http://ruby-doc.org/core-1.9.3/Object.html#method-i-send – wyattisimo Jan 10 '13 at 21:30
  • what if the `prop_name ` is nested key ? `foo.bar` – user181452 Jan 13 '17 at 15:53
  • In a One to Many relationship, eg: Posts have many Comments, how to dynamically push the depedent instance to the independent? Normally, we do like this on rails: `@post.comments << @comment`, how to turn it dynamic? I know that ActiveModel automatically updates the independent when I set it on the dependent, but just for curiosity :) – João Ramires Feb 17 '21 at 14:29
  • wouldn't `public_send` be safer? – molexi Oct 18 '21 at 20:32
16

Object#instance_variable_set() is what you are looking for, and is the cleaner version of what you wanted.

Example:

your_object = Object.new
your_object.instance_variable_set(:@attribute, 'value')
your_object
# => <Object:0x007fabda110408 @attribute="value">

Ruby documentation about Object#instance_variable_set

Jochem Schulenklopper
  • 6,452
  • 4
  • 44
  • 62
  • Assuming that the OP really wants to set an instance variable rather than calling the mutator method. The question appears to be conflating the two but the example code at least is calling the mutator method. – mu is too short Dec 20 '15 at 21:27
  • @mu-is-too-short, Sure, but `instance_variable_set()` really sets an instance variable for a variable name that is determined at run-time. In that sense, it has the same effect as in the question, but a cleaner / clearer alternative. – Jochem Schulenklopper Dec 22 '15 at 07:59
  • 1
    Not necessarily. Directly setting an instance variable and calling a mutator method aren't always the same thing; the method can alter/check the incoming value, the method may store the value somewhere else, there might not even be an instance variable behind the mutator, ... That's all I'm saying. – mu is too short Dec 22 '15 at 18:30
9

If circumstances allow for an instance method, the following is not overly offensive:

class P00t
  attr_reader :w00t

  def set_property(name, value)
    prop_name = "@#{name}".to_sym # you need the property name, prefixed with a '@', as a symbol
    self.instance_variable_set(prop_name, value)
  end
end

Usage:

p = P00t.new
p.set_property('w00t', 'jeremy')
Benjineer
  • 1,530
  • 18
  • 22
5

This answer (https://stackoverflow.com/a/7466385/6094965) worked for me:

Object.send(attribute + '=', value)

attribute has to be a String. So if you are iterating trough an array of Symbols (like me), you can use to_s.

Object.send(attribute.to_s + '=', value) 
Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
Tincho Rockss
  • 51
  • 1
  • 3