In Ruby 1.9.3 I need to create a few class instances which each have similar instance- and class-methods but which vary only by a few fixed parameters. The distinction of their class type is also important so I cannot simply use separate instances of the same class.
A simplified example looks like this.
module Animal
private
def self.make_animal(name, legs, noise)
klass = Class.new
klass.const_set(:NUM_LEGS, legs)
klass.class.send(:define_method, :scream) { noise.upcase + '!' }
Animal.const_set(name, klass)
end
make_animal :Tiger, 4, 'roar'
make_animal :Human, 2, 'derp'
end
This seems to work fine except that the variables used in the block which dynamically defines the "scream" method are bound at runtime of the "scream" method instead of runtime of the "make_animal" method.
Animal::Human::NUM_LEGS # => 2 -- ok
Animal::Tiger::NUM_LEGS # => 4 -- ok
Animal::Human.scream # => "DERP!" -- ok
Animal::Tiger.scream # => "DERP!" -- fail!
How can I modify the above code so that the Tiger screams "ROAR!"
?
[Note] I really do need to maintain the goofy OO structure in the example for reasons that are too involved to describe here. I'm interested only in learning how to programmatically define class methods on dynamically defined classes with parameterized method implementations.
klass.class
is the same in both cases (Class): all classes are instances of Class
. As a result you're defining scream and then redefining it.
What are often thought of as class methods in ruby are actually singleton methods (there's lots of stuff to read about eigenclasses etc if you are interested).
The
def some_object.foo
end
Construct creates singleton methods. Very often this will be inside a class definition, using self but you can do it on anything, for example if you do
x = 'dog'
def x.bark
"Woof"
end
Then x.bark will return woof, but bark won't be defined on any other string.
Here your method needs to reference your noise
variable, so you'll need to use define_singleton_method
to define your method.
If you're still in ruby 1.8 you can't use define_singleton_method - you need to use the fact that singleton methods are methods on the eigenclass.
klass = Class.new
eigenclass = class << klass; self; end
eigenclass.send(:define_method, :scream){noise}
Is equivalent to using define_singleton_method
The problem with your code is not wrong binding moment. It's that you are defining method on Class#class
. And it is, surprise-surprise, Class
, the one and only. So you are overwriting "roar" version with "derp" version.
Instead, you should define methods on those dynamic classes directly. Here's my take (it's using instance var for the noise
, hope that's not a problem).
module Animal
private
def self.make_animal(name, legs, noise)
klass = Class.new
klass.const_set(:NUM_LEGS, legs)
klass.instance_variable_set(:@noise, noise)
klass.instance_eval do |k|
def scream
@noise.upcase + '!'
end
end
Animal.const_set(name, klass)
end
make_animal :Tiger, 4, 'roar'
make_animal :Human, 2, 'derp'
end
Animal::Human::NUM_LEGS # => 2
Animal::Tiger::NUM_LEGS # => 4
Animal::Human.scream # => "DERP!"
Animal::Tiger.scream # => "ROAR!"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With