Adding an instance variable to a class in Ruby - ruby

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.

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\">

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.

#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!

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

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

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...

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

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"

Related

Reflection in ruby?

I am curious how this works. For example if I create a factory pattern based class where you can "register" classes for later use and then do something like
FactoryClass.register('YourClassName', [param, param, ...]);
FactoryClass.create('your_class_name').call_method_from_this_object
where 'class_name' is a key in a hash that maps to value: ClassName
is there anything like php reflection, where I can create an instance of a class based on a string name and pass in the arguments in? (in php the arguments would be an array of them that php then knows how what to do with)
So if we take a real world example:
class Foo
attr_reader :something
def initialize(input)
#something = input
end
def get_something
return #something
end
end
# In the factory class, foo is then placed in a hash: {'foo' => 'Foo'}
# This step might not be required??
FactoryClass.create('Foo', ['hello'])
# Some where in your code:
FactoryClass.create('foo').get_something # => hello
Is this possible to do in ruby? I know everything is essentially an object, but I haven't seen any API or docs on creating class instances from string names like this and also passing in objects.
As for the hash above, thinking about it now I would probably have to do something like:
{'foo' => {'class' => 'Foo', 'params' => [param, param, ...]}}
This way when you call .create on the FactoryClass it would know, ok I can instantiate Foo with the associated params.
If I am way off base, please feel free to educate me.
Check out Module#const_get (retrieving a constant from a String) and Object#send (calling a method from a String).
Here is an answer that doesn't use eval.
PHP's Reflection is called Metaprogramming in Ruby, but they are quite different. Everything in Ruby is open and could be accessed.
Consider the following code:
class Foo
attr_reader :something
def initialize(input)
#something = input
end
def get_something
return #something
end
end
#registered = { }
def register(reference_name, class_name, params=[])
#registered[reference_name] = { class_name: class_name, params: [params].flatten }
end
def create(reference_name)
h = #registered[reference_name]
Object.const_get(h[:class_name]).new(*(h[:params]))
end
register('foo', 'Foo', ['something'])
puts create('foo').get_something
You can use Object#const_get to get objects from strings. Object.const_get('Foo') will give you the object Foo.
However, you don't need to send class name as string. You can also pass around the class name as object and use that directly.
class Foo
attr_reader :something
def initialize(input)
#something = input
end
def get_something
return #something
end
end
#registered = { }
def register(reference_name, class_name, params=[])
#registered[reference_name] = { class_name: class_name, params: [params].flatten }
end
def create(reference_name)
h = #registered[reference_name]
h[:class_name].new(*(h[:params]))
end
register('foo', Foo, ['something else'])
puts create('foo').get_something
Actually one of the strong points in ruby is meta-programming. So this is really easy to do in ruby.
I am going to skip the registering part, and jump straight to the creation
A simple implementation would be this
class FactoryClass
def self.create(class_name, params)
klass = Object.const_get(class_name)
klass.new(*params)
end
end
and then you can just do:
FactoryClass.create('YourClassName', [param, param, ...]);
and this would be equivalent to calling
YourClassName.new(param, param, ...)

Ruby create methods from a hash

I have the following code I am using to turn a hash collection into methods on my classes (somewhat like active record). The problem I am having is that my setter is not working. I am still quite new to Ruby and believe I've gotten myself turned around a bit.
class TheClass
def initialize
#properties = {"my hash"}
self.extend #properties.to_methods
end
end
class Hash
def to_methods
hash = self
Module.new do
hash.each_pair do |key, value|
define_method key do
value
end
define_method("#{key}=") do |val|
instance_variable_set("##{key}", val)
end
end
end
end
end
The methods are created and I can read them on my class but setting them does not work.
myClass = TheClass.new
item = myClass.property # will work.
myClass.property = item # this is what is currently not working.
If your goal is to set dynamic properties then you could use OpenStruct.
require 'ostruct'
person = OpenStruct.new
person.name = "Jennifer Tilly"
person.age = 52
puts person.name
# => "Jennifer Tilly"
puts person.phone_number
# => nil
It even has built-in support to create them from a hash
hash = { :name => "Earth", :population => 6_902_312_042 }
planet = OpenStruct.new(hash)
Your getter method always returns the value in the original hash. Setting the instance variable won't change that; you need to make the getter refer to the instance variable. Something like:
hash.each_pair do |key, value|
define_method key do
instance_variable_get("##{key}")
end
# ... define the setter as before
end
And you also need to set the instance variables at the start, say by putting
#properties.each_pair do |key,val|
instance_variable_set("##{key}",val)
end
in the initialize method.
Note: I do not guarantee that this is the best way to do it; I am not a Ruby expert. But it does work.
It works just fine for me (after fixing the obvious syntax errors in your code, of course):
myClass.instance_variable_get(:#property) # => nil
myClass.property = 42
myClass.instance_variable_get(:#property) # => 42
Note that in Ruby instance variables are always private and you never define a getter for them, so you cannot actually look at them from the outside (other than via reflection), but that doesn't mean that your code doesn't work, it only means that you cannot see that it works.
This is essentially what I was suggesting with method_missing. I'm not familiar enough with either route to say why or why not to use it which is why I asked above. Essentially this will auto-generate properties for you:
def method_missing sym, *args
name = sym.to_s
aname = name.sub("=","")
self.class.module_eval do
attr_accessor aname
end
send name, args.first unless aname == name
end

Is there an elegant way to test if one instance method is an alias for another?

In a unit test I need to test whether alias methods defined by alias_method have been properly defined. I could simply use the same tests on the aliases used for their originals, but I'm wondering whether there's a more definitive or efficient solution. For instance, is there a way to 1) dereference a method alias and return its original's name, 2) get and compare some kind of underlying method identifier or address, or 3) get and compare method definitions? For example:
class MyClass
def foo
# do something
end
alias_method :bar, :foo
end
describe MyClass do
it "method bar should be an alias for method foo" do
m = MyClass.new
# ??? identity(m.bar).should == identity(m.foo) ???
end
end
Suggestions?
According to the documentation for Method,
Two method objects are equal if that
are bound to the same object and
contain the same body.
Calling Object#method and comparing the Method objects that it returns will verify that the methods are equivalent:
m.method(:bar) == m.method(:foo)
bk1e's method works most of the time, but I just happened to hit the case where it doesn't work:
class Stream
class << self
alias_method :open, :new
end
end
open = Stream.method(:open)
new = Stream.method(:new)
p open, new # => #<Method: Stream.new>, #<Method: Class#new>
p open.receiver, new.receiver # => Stream, Stream
p open == new # => false
The output is produced in Ruby 1.9, not sure if it's a bug or not since Ruby 1.8 produces true for the last line. So, if you are using 1.9, be careful if you are aliasing an inherited class method (like Class#new), These two methods are bound to the same object (the class object Stream), but they are considered not equivalent by Ruby 1.9.
My workaround is simple - alias the original method again and test the equality of the two aliases:
class << Stream; alias_method :alias_test_open, :new; end
open = Stream.method(:open)
alias_test_open = Stream.method(:alias_test_open)
p open, alias_test_open # => #<Method: Stream.new>, #<Method: Stream.new>
p open.receiver, alias_test_open.receiver # => Stream, Stream
p open == alias_test_open # => true
Hope this helps.
UPDATE:
See http://bugs.ruby-lang.org/issues/7613
So Method#== should return false in this case since a super call would invoke different methods; it is not a bug.
Calling MyClass.instance_method(:foo) will result UnboundMethod instance, which has eql? method.
So the answer is:
describe MyClass do
subject { described_class }
specify do
expect(subject.instance_method(:foo)).to be_eql(subject.instance_method(:bar))
end
end

How do I call a method that is a hash value?

Previously, I asked about a clever way to execute a method on a given condition "Ruby a clever way to execute a function on a condition."
The solutions and response time was great, though, upon implementation, having a hash of lambdas gets ugly quite quickly. So I started experimenting.
The following code works:
def a()
puts "hello world"
end
some_hash = { 0 => a() }
some_hash[0]
But if I wrap this in a class it stops working:
class A
#a = { 0 => a()}
def a()
puts "hello world"
end
def b()
#a[0]
end
end
d = A.new()
d.b()
I can't see why it should stop working, can anyone suggest how to make it work?
that code doesn't work. it executes a at the time it is added to the hash, not when it is retrieved from the hash (try it in irb).
It doesn't work in the class because there is no a method defined on the class (you eventually define a method a on the instance.
Try actually using lambdas like
{0 => lambda { puts "hello world" }}
instead
First of all, you are not putting a lambda in the hash. You're putting the result of calling a() in the current context.
Given this information, consider what the code in your class means. The context of a class definition is the class. So you define an instance method called a, and assign a class instance variable to the a hash containing the result of calling a in the current context. The current context is the class A, and class A does not have a class method called a, so you're trying to put the result of a nonexistent method there. Then in the instance method b, you try to access an instance variable called #a -- but there isn't one. The #a defined in the class context belongs to the class itself, not any particular instance.
So first of all, if you want a lambda, you need to make a lambda. Second, you need to be clear about the difference between a class and an instance of that class.
If you want to make a list of method names to be called on certain conditions, you can do it like this:
class A
def self.conditions() { 0 => :a } end
def a
puts "Hello!"
end
def b(arg)
send self.class.conditions[arg]
end
end
This defines the conditions hash as a method of the class (making it easy to access), and the hash merely contains the name of the method to call rather than a lambda or anything like that. So when you call b(0), it sends itself the message contained in A.conditions[0], which is a.
If you really just want to pretty this sort of thing up,
why not wrap all your methods in a class like so:
# a container to store all your methods you want to use a hash to access
class MethodHash
alias [] send
def one
puts "I'm one"
end
def two
puts "I'm two"
end
end
x = MethodHash.new
x[:one] # prints "I'm one"
x.two # prints "I'm one"
or, to use your example:
# a general purpose object that transforms a hash into calls on methods of some given object
class DelegateHash
def initialize(target, method_hash)
#target = target
#method_hash = method_hash.dup
end
def [](k)
#target.send(#method_hash[k])
end
end
class A
def initialize
#a = DelegateHash.new(self, { 0 => :a })
end
def a()
puts "hello world"
end
def b()
#a[0]
end
end
x = A.new
x.a #=> prints "hello world"
x.b #=> prints "hello world"
One other basic error that you made is that you initialized #a outside of any instance method -
just bare inside of the definition of A. This is a big time no-no, because it just doesn't work.
Remember, in ruby, everything is an object, including classes, and the # prefix means the instance
variable of whatever object is currently self. Inside an instance method definitions, self is an instance
of the class. But outside of that, just inside the class definition, self is the class object - so you defined
an instance variable named #a for the class object A, which none of the instances of A can get to directly.
Ruby does have a reason for this behaviour (class instance variables can be really handy if you know what
you're doing), but this is a more advanced technique.
In short, only initialize instance variables in the initialize method.
table = {
:a => 'test',
:b => 12,
:c => lambda { "Hallo" },
:d => def print(); "Hallo in test"; end
}
puts table[:a]
puts table[:b]
puts table[:c].call
puts table[:d].send( :print )
Well, the first line in your class calls a method that doesn't exist yet. It won't even exist after the whole class is loaded though, since that would be a call to the class method and you've only defined instance methods.
Also keep in mind that {0 => a()} will call the method a(), not create a reference to the method a(). If you wanted to put a function in there that doesn't get evaluated until later, you'd have to use some kind of Lambda.
I am pretty new to using callbacks in Ruby and this is how I explained it to myself using an example:
require 'logger'
log = Logger.new('/var/tmp/log.out')
def callit(severity, msg, myproc)
myproc.call(sev, msg)
end
lookup_severity = {}
lookup_severity['info'] = Proc.new { |x| log.info(x) }
lookup_severity['debug'] = Proc.new { |x| log.debug(x) }
logit = Proc.new { |x,y| lookup_sev[x].call(y) }
callit('info', "check4", logit)
callit('debug', "check5", logit)
a = ->(string="No string passed") do
puts string
end
some_hash = { 0 => a }
some_hash[0].call("Hello World")
some_hash[0][]

Initialize a Ruby class from an arbitrary hash, but only keys with matching accessors

Is there a simple way to list the accessors/readers that have been set in a Ruby Class?
class Test
attr_reader :one, :two
def initialize
# Do something
end
def three
end
end
Test.new
=> [one,two]
What I'm really trying to do is to allow initialize to accept a Hash with any number of attributes in, but only commit the ones that have readers already defined. Something like:
def initialize(opts)
opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val|
eval("##{opt} = \"#{val}\"")
end
end
Any other suggestions?
This is what I use (I call this idiom hash-init).
def initialize(object_attribute_hash = {})
object_attribute_hash.map { |(k, v)| send("#{k}=", v) }
end
If you are on Ruby 1.9 you can do it even cleaner (send allows private methods):
def initialize(object_attribute_hash = {})
object_attribute_hash.map { |(k, v)| public_send("#{k}=", v) }
end
This will raise a NoMethodError if you try to assign to foo and method "foo=" does not exist. If you want to do it clean (assign attrs for which writers exist) you should do a check
def initialize(object_attribute_hash = {})
object_attribute_hash.map do |(k, v)|
writer_m = "#{k}="
send(writer_m, v) if respond_to?(writer_m) }
end
end
however this might lead to situations where you feed your object wrong keys (say from a form) and instead of failing loudly it will just swallow them - painful debugging ahead. So in my book a NoMethodError is a better option (it signifies a contract violation).
If you just want a list of all writers (there is no way to do that for readers) you do
some_object.methods.grep(/\w=$/)
which is "get an array of method names and grep it for entries which end with a single equals sign after a word character".
If you do
eval("##{opt} = \"#{val}\"")
and val comes from a web form - congratulations, you just equipped your app with a wide-open exploit.
You could override attr_reader, attr_writer and attr_accessor to provide some kind of tracking mechanism for your class so you can have better reflection capability such as this.
For example:
class Class
alias_method :attr_reader_without_tracking, :attr_reader
def attr_reader(*names)
attr_readers.concat(names)
attr_reader_without_tracking(*names)
end
def attr_readers
#attr_readers ||= [ ]
end
alias_method :attr_writer_without_tracking, :attr_writer
def attr_writer(*names)
attr_writers.concat(names)
attr_writer_without_tracking(*names)
end
def attr_writers
#attr_writers ||= [ ]
end
alias_method :attr_accessor_without_tracking, :attr_accessor
def attr_accessor(*names)
attr_readers.concat(names)
attr_writers.concat(names)
attr_accessor_without_tracking(*names)
end
end
These can be demonstrated fairly simply:
class Foo
attr_reader :foo, :bar
attr_writer :baz
attr_accessor :foobar
end
puts "Readers: " + Foo.attr_readers.join(', ')
# => Readers: foo, bar, foobar
puts "Writers: " + Foo.attr_writers.join(', ')
# => Writers: baz, foobar
Try something like this:
class Test
attr_accessor :foo, :bar
def initialize(opts = {})
opts.each do |opt, val|
send("#{opt}=", val) if respond_to? "#{opt}="
end
end
end
test = Test.new(:foo => "a", :bar => "b", :baz => "c")
p test.foo # => nil
p test.bar # => nil
p test.baz # => undefined method `baz' for #<Test:0x1001729f0 #bar="b", #foo="a"> (NoMethodError)
This is basically what Rails does when you pass in a params hash to new. It will ignore all parameters it doesn't know about, and it will allow you to set things that aren't necessarily defined by attr_accessor, but still have an appropriate setter.
The only downside is that this really requires that you have a setter defined (versus just the accessor) which may not be what you're looking for.
Accessors are just ordinary methods that happen to access some piece of data. Here's code that will do roughly what you want. It checks if there's a method named for the hash key and sets an accompanying instance variable if so:
def initialize(opts)
opts.each do |opt,val|
instance_variable_set("##{opt}", val.to_s) if respond_to? opt
end
end
Note that this will get tripped up if a key has the same name as a method but that method isn't a simple instance variable access (e.g., {:object_id => 42}). But not all accessors will necessarily be defined by attr_accessor either, so there's not really a better way to tell. I also changed it to use instance_variable_set, which is so much more efficient and secure it's ridiculous.
There's no built-in way to get such a list. The attr_* functions essentially just add methods, create an instance variable, and nothing else. You could write wrappers for them to do what you want, but that might be overkill. Depending on your particular circumstances, you might be able to make use of Object#instance_variable_defined? and Module#public_method_defined?.
Also, avoid using eval when possible:
def initialize(opts)
opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val|
instance_variable_set "##{opt}", val
end
end
You can look to see what methods are defined (with Object#methods), and from those identify the setters (the last character of those is =), but there's no 100% sure way to know that those methods weren't implemented in a non-obvious way that involves different instance variables.
Nevertheless Foo.new.methods.grep(/=$/) will give you a printable list of property setters. Or, since you have a hash already, you can try:
def initialize(opts)
opts.each do |opt,val|
instance_variable_set("##{opt}", val.to_s) if respond_to? "#{opt}="
end
end

Resources