instance_eval с пользовательским определенным контекстом
Я пытаюсь создать настройку 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 переключается на 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