40

How can I add an instance variable to a defined class at runtime, and later get and set its value from outside of the class?

I'm looking for a metaprogramming solution that allows me to modify the class instance at runtime instead of modifying the source code that originally defined the class. A few of the solutions explain how to declare instance variables in the class definitions, but that is not what I am asking about.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
readonly
  • 343,444
  • 107
  • 203
  • 205
  • 1
    It may not have been made clear, but you can literally reopen the class at any moment with class ClassName; (new stuff here); end. This does add stuff to an existing class. Neat, huh? – webmat Sep 30 '08 at 01:19
  • Yeah, webmat is right... All the responses below work in any file you want whenever you want... because of a thing called "open classes" which means you can modify a class anytime you want via the class keyword. – Mike Stone Sep 30 '08 at 01:36

8 Answers8

67

Ruby provides methods for this, instance_variable_get and instance_variable_set. (docs)

You can create and assign a new instance variables like this:

>> foo = Object.new
=> #<Object:0x2aaaaaacc400>

>> foo.instance_variable_set(:@bar, "baz")
=> "baz"

>> foo.inspect
=> #<Object:0x2aaaaaacc400 @bar=\"baz\">
Gordon Wilson
  • 26,244
  • 11
  • 57
  • 60
  • This works if you just want it for a particular instance of the class... but I would tend to prefer to define singleton methods on the instance rather than use those methods – Mike Stone Sep 30 '08 at 01:02
  • 3
    Mike, I think that is the actual goal of this question. He is asking how to add a variable "at runtime", hence the use of instance_variable_set instead of defining it in the code. More than likely "baz" would be another variable as well. – Sixty4Bit Sep 30 '08 at 01:11
  • singleton method will still achieve the same thing, see my IRB output at the bottom of my answer. – Mike Stone Sep 30 '08 at 01:25
17

You can use attribute accessors:

class Array
  attr_accessor :var
end

Now you can access it via:

array = []
array.var = 123
puts array.var

Note that you can also use attr_reader or attr_writer to define just getters or setters or you can define them manually as such:

class Array
  attr_reader :getter_only_method
  attr_writer :setter_only_method

  # Manual definitions equivalent to using attr_reader/writer/accessor
  def var
    @var
  end

  def var=(value)
    @var = value
  end
end

You can also use singleton methods if you just want it defined on a single instance:

array = []

def array.var
  @var
end

def array.var=(value)
  @var = value
end

array.var = 123
puts array.var

FYI, in response to the comment on this answer, the singleton method works fine, and the following is proof:

irb(main):001:0> class A
irb(main):002:1>   attr_accessor :b
irb(main):003:1> end
=> nil
irb(main):004:0> a = A.new
=> #<A:0x7fbb4b0efe58>
irb(main):005:0> a.b = 1
=> 1
irb(main):006:0> a.b
=> 1
irb(main):007:0> def a.setit=(value)
irb(main):008:1>   @b = value
irb(main):009:1> end
=> nil
irb(main):010:0> a.setit = 2
=> 2
irb(main):011:0> a.b
=> 2
irb(main):012:0> 

As you can see, the singleton method setit will set the same field, @b, as the one defined using the attr_accessor... so a singleton method is a perfectly valid approach to this question.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Mike Stone
  • 44,224
  • 30
  • 113
  • 140
  • If you are going to use singleton methods then you should be acting on class variables instead of instance variables. This will not give you the outcome you are looking for. – Sixty4Bit Sep 30 '08 at 01:13
  • 1
    Check out _why's explaination here: http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html – Sixty4Bit Sep 30 '08 at 01:13
  • see my response to your comment in my answer... it works as I advertised. – Mike Stone Sep 30 '08 at 01:23
  • It will work.. you can add instance and class members by modifying the singleton/metaclass class for the object. – Gishu Sep 30 '08 at 01:56
  • Thanks Gishu :-) I don't know why Sixty4Bit doesn't think it will work. I use singleton methods all the time if I don't want to make the change to the class itself. Plus they are a really cool concept, so I love to use them in general. – Mike Stone Sep 30 '08 at 02:10
  • The Why article is great for defining singleton methods via a closure so you have access to the surrounding scope, but if you don't need access to the scope, I think an explicit singleton method is better. – Mike Stone Sep 30 '08 at 02:24
  • Big answer. Why not just recommend a book? :) – Ian Terrell Sep 30 '08 at 02:56
  • Lol, well most of it is code examples... but also Ruby often has many ways to do the same thing, so I was presenting a couple alternatives, and responding to Sixty4Bit who mistakenly thought my answer was incorrect. – Mike Stone Sep 30 '08 at 03:08
15

@Readonly

If your usage of "class MyObject" is a usage of an open class, then please note you are redefining the initialize method.

In Ruby, there is no such thing as overloading... only overriding, or redefinition... in other words there can only be 1 instance of any given method, so if you redefine it, it is redefined... and the initialize method is no different (even though it is what the new method of Class objects use).

Thus, never redefine an existing method without aliasing it first... at least if you want access to the original definition. And redefining the initialize method of an unknown class may be quite risky.

At any rate, I think I have a much simpler solution for you, which uses the actual metaclass to define singleton methods:

m = MyObject.new
metaclass = class << m; self; end
metaclass.send :attr_accessor, :first, :second
m.first = "first"
m.second = "second"
puts m.first, m.second

You can use both the metaclass and open classes to get even trickier and do something like:

class MyObject
  def metaclass
    class << self
      self
    end
  end

  def define_attributes(hash)
    hash.each_pair { |key, value|
      metaclass.send :attr_accessor, key
      send "#{key}=".to_sym, value
    }
  end
end

m = MyObject.new
m.define_attributes({ :first => "first", :second => "second" })

The above is basically exposing the metaclass via the "metaclass" method, then using it in define_attributes to dynamically define a bunch of attributes with attr_accessor, and then invoking the attribute setter afterwards with the associated value in the hash.

With Ruby you can get creative and do the same thing many different ways ;-)


FYI, in case you didn't know, using the metaclass as I have done means you are only acting on the given instance of the object. Thus, invoking define_attributes will only define those attributes for that particular instance.

Example:

m1 = MyObject.new
m2 = MyObject.new
m1.define_attributes({:a => 123, :b => 321})
m2.define_attributes({:c => "abc", :d => "zxy"})
puts m1.a, m1.b, m2.c, m2.d # this will work
m1.c = 5 # this will fail because c= is not defined on m1!
m2.a = 5 # this will fail because a= is not defined on m2!
Community
  • 1
  • 1
Mike Stone
  • 44,224
  • 30
  • 113
  • 140
  • Won't this define these methods a MyObject object and not the class itself? So this would be defining a m1.a, m1.b where a and b are singleton methods for only that object. – vrish88 Aug 05 '10 at 23:04
  • @Mike Why am I getting error `in '
    ': private method 'first=' called for # (NoMethodError)` on your first code snippet?
    – Knight of Ni Nov 13 '13 at 07:08
2

Mike Stone's answer is already quite comprehensive, but I'd like to add a little detail.

You can modify your class at any moment, even after some instance have been created, and get the results you desire. You can try it out in your console:

s1 = 'string 1'
s2 = 'string 2'

class String
  attr_accessor :my_var
end

s1.my_var = 'comment #1'
s2.my_var = 'comment 2'

puts s1.my_var, s2.my_var
Community
  • 1
  • 1
webmat
  • 58,466
  • 12
  • 54
  • 59
2

The other solutions will work perfectly too, but here is an example using define_method, if you are hell bent on not using open classes... it will define the "var" variable for the array class... but note that it is EQUIVALENT to using an open class... the benefit is you can do it for an unknown class (so any object's class, rather than opening a specific class)... also define_method will work inside a method, whereas you cannot open a class within a method.

array = []
array.class.send(:define_method, :var) { @var }
array.class.send(:define_method, :var=) { |value| @var = value }

And here is an example of it's use... note that array2, a DIFFERENT array also has the methods, so if this is not what you want, you probably want singleton methods which I explained in another post.

irb(main):001:0> array = []
=> []
irb(main):002:0> array.class.send(:define_method, :var) { @var }
=> #<Proc:0x00007f289ccb62b0@(irb):2>
irb(main):003:0> array.class.send(:define_method, :var=) { |value| @var = value }
=> #<Proc:0x00007f289cc9fa88@(irb):3>
irb(main):004:0> array.var = 123
=> 123
irb(main):005:0> array.var
=> 123
irb(main):006:0> array2 = []
=> []
irb(main):007:0> array2.var = 321
=> 321
irb(main):008:0> array2.var
=> 321
irb(main):009:0> array.var
=> 123
Mike Stone
  • 44,224
  • 30
  • 113
  • 140
0

I wrote a gem for this some time ago. It's called "Flexible" and not available via rubygems, but was available via github until yesterday. I deleted it because it was useless for me.

You can do

class Foo
    include Flexible
end
f = Foo.new
f.bar = 1

with it without getting any error. So you can set and get instance variables from an object on the fly. If you are interessted... I could upload the source code to github again. It needs some modification to enable

f.bar?
#=> true

as method for asking the object if a instance variable "bar" is defined or not, but anything else is running.

Kind regards, musicmatze

musicmatze
  • 4,124
  • 7
  • 33
  • 48
  • That seems very dangerous. At the very least, you turn typos into runnable code. I can't think of a situation where this would be necessary, let alone be a good idea. – Huliax Oct 20 '15 at 18:38
0

Readonly, in response to your edit:

Edit: It looks like I need to clarify that I'm looking for a metaprogramming solution that allows me to modify the class instance at runtime instead of modifying the source code that originally defined the class. A few of the solutions explain how to declare instance variables in the class definitions, but that is not what I am asking about. Sorry for the confusion.

I think you don't quite understand the concept of "open classes", which means you can open up a class at any time. For example:

class A
  def hello
    print "hello "
  end
end

class A
  def world
    puts "world!"
  end
end

a = A.new
a.hello
a.world

The above is perfectly valid Ruby code, and the 2 class definitions can be spread across multiple Ruby files. You could use the "define_method" method in the Module object to define a new method on a class instance, but it is equivalent to using open classes.

"Open classes" in Ruby means you can redefine ANY class at ANY point in time... which means add new methods, redefine existing methods, or whatever you want really. It sounds like the "open class" solution really is what you are looking for...

Mike Stone
  • 44,224
  • 30
  • 113
  • 140
0

It looks like all of the previous answers assume that you know what the name of the class that you want to tweak is when you are writing your code. Well, that isn't always true (at least, not for me). I might be iterating over a pile of classes that I want to bestow some variable on (say, to hold some metadata or something). In that case something like this will do the job,

# example classes that we want to tweak
class Foo;end
class Bar;end
klasses = [Foo, Bar]

# iterating over a collection of klasses
klasses.each do |klass|
  # #class_eval gets it done
  klass.class_eval do
    attr_accessor :baz
  end
end

# it works
f = Foo.new
f.baz # => nil
f.baz = 'it works' # => "it works"
b = Bar.new
b.baz # => nil
b.baz = 'it still works' # => "it still works"
Huliax
  • 1,489
  • 3
  • 15
  • 27