When you refer to something
in Ruby, it first look up something
in local bindings, if it fails, it then look up self.something
. self
represents a context of the evaluation, and this context changes on class definition class C; self; end
, method definition class C; def m; self; end; end
, however, it won't change on block definition. The block captures the current self
at the point of block definition.
module Command
define :quit do
foo "bar" # self is Command, calls Command.foo by default
end
end
If you want to modify the self
context inside a block, you can use BasicObject.instance_eval
(or instance_exec
, class_eval
, class_exec
).
For your example, the block passed to define
should be evaluated under the self
context of an instance of the concrete command.
Here is an example. I added some mock method definition in class Command::Command
:
module Command
class Command
# remove this accessor if you want to make `name` readonly
attr_accessor :name
def exec(&block)
@exec = block
end
def foo(msg)
puts "FOO => #{msg}"
end
def run
@exec.call if @exec
end
end
def self.define(name, &block)
klass = Class.new(Command) do
define_method(:initialize) do
method(:name=).call(name) # it would be better to make it readonly
instance_eval(&block)
end
# readonly
# define_method(:name) { name }
end
::Command.const_set("Command_#{name}", klass)
end
define :quit do
foo "bar"
exec do
puts "EXEC => #{name}"
end
end
end
quit = Command::Command_quit.new #=> FOO => bar
quit.run #=> EXEC => quit
puts quit.class #=> Command::Command_quit