Reputation: 22225
This part of code dynamically creates several classes:
(1..MAX_ACT).each do |act_id|
klass = Class.new(ActB) do
def initialize(trg)
super(trg, act_id)
end
end
Object.const_set("Act#{act_id}", klass)
end
In this case, the common base class (ActB
) has a constructor with two parameters, while the child classes have a constructor with one parameter.
Running this code works well, but when I later try to instantiate one of these classes, for example
Act3.new(4)
I get the error message
NameError: undefined local variable or method `act_id' for #<Act3:0x00000006008b7990>
The error message must refer to the line
super(trg, act_id)
because this is the only place in my program where I am using this variable. However, this variable is defined a few lines above, when it says
(1..MAX_ACT).each do |act_id|
I had expected, that the do...end block creates a closure for the constructor, where act_id is bound. However, this doesn't seem to be the case.
Why does my example not work? How do I have to do it correctly?
Upvotes: 3
Views: 254
Reputation: 121000
Just out of curiosity, there is a hack, allowing to fool scoping and still use def initialize
:)
class ActB
def initialize(trg, act_id)
puts "ActID: #{act_id}"
end
end
(1..MAX_ACT).each do |act_id|
klass = Class.new(ActB) do
@act_id = act_id
def initialize(trg)
super(trg, self.class.instance_variable_get(:@act_id))
end
end
Object.const_set("Act#{act_id}", klass)
end
Act1.new :foo
#⇒ ActID: 1
Act2.new :foo
#⇒ ActID: 2
Upvotes: 1
Reputation: 4420
def
(and class
and module
) creates a fresh local scope, which doesn't inherit any locals from outside.
So you're right that the Class.new do .. end
creates a closure... but the inner def
doesn't share it.
If you need standard block behaviour, you can use define_method
instead:
(1..MAX_ACT).each do |act_id|
klass = Class.new(ActB) do
define_method :initialize do |trg|
super(trg, act_id)
end
end
Object.const_set("Act#{act_id}", klass)
end
Upvotes: 4
Reputation: 1984
The problem here is that the block passed to Class.new
is executed in the context of that class. In the context of that class, act_id
is not defined. So, to fix this, you can move the method definition outside of the class initialization, like so:
(1..MAX_ACT).each do |act_id|
klass = Class.new(ActB)
klass.define_method(:initialize) do |trg|
super(trg, act_id)
end
Object.const_set("Act#{act_id}", klass)
end
Upvotes: 0