我试图为已经存在的库创建 DSL 定制,但我对 Ruby 块上下文有一些误解。
假设我们有一个块保存为 proc
some_block = Proc.new do
def testing; end
puts self.inspect
if self.kind_of? Class
puts self.instance_methods.include? :testing
else
puts self.methods.include? :testing
end
puts self.singleton_class.instance_methods.include? :testing
implicit_calling_context
end
def implicit_calling_context
"implicit calling context is definition context"
end
当我们简单地产生一个块时,这个块的自身上下文不会改变
class N
def some_method
yield
end
def self.implicit_calling_context
puts "implicit calling context is N"
end
def implicit_calling_context
puts "implicit calling context is N instance"
end
end
N.new.some_method &some_block
# => main # block self context stays definition one (closure)
# false
# false
# implicit calling context is definition context
当我们打电话
N.class_eval &some_block
# => N # block self context changed to N
# true # block definition context became N
# false
# implicit calling context is N
此块中的 self 变为 N,默认定义保持不变
当我们在实例上调用 instance_eval
N.new.instance_eval &some_block
# => #<N:0x007fc0422294f8>
# true
# true
# implicit calling context is N instance
some_block 中的 self 上下文切换到 N 个实例,但默认定义者变为 N 个实例元类
是否有任何方便的方法可以在其他地方的实例和代理定义上下文中产生块?
例如,我有 Delegator 实例,其中包含一些类,我想将定义上下文代理到它:
class Definee
end
class MyDelegator < SimpleDelegator
def work *args
puts "some additional work"
__getobj__.work *args
end
end
MyDelegator.new(Definee).instance_eval do
work "some base work"
def test_my_work
"should be defined on Definee"
end
end
# I expecting that test_my_work should be defined as Definee instance method
# and :work should be called on MyDelegator.new(Definee) instance
# with "some base work" arg.
所以 Definee 已经实现了 DSL,我用 instance_eval 覆盖了它,但是定义上下文不正确。Class_eval 将被委托给 Definee,并且不会调用 MyDelegator 的任何方法,因此它也不能解决问题。
也许有一些更优雅的方式来做这样的事情。有任何想法吗?
编辑:
使用从 Module 继承的类作为委托人解决了我的定义上下文切换问题。
class Definee
end
class MyDelegator < Module
def initialize definee, &block
@definee = definee
self.class_eval &block
@definee.include self
end
def work *args
puts "some additional work"
@definee.work *args
end
def method_missing *args, &block
@definee.send *args, &block
end
end
MyDelegator.new(Definee) do
work "some base work"
def test_my_work
"should be defined on Definee"
end
end