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

0 ответов

Другие вопросы по тегам