Ссылка на вызывающий объект в переданном блоке в Ruby

Есть ли способ получить вызываемый объект внутри вызываемого блока. Например, есть ли способ для блоков получить доступ к области действия метода batman или класса SuperHeros?

class SuperHeros

  attr_accessor :news

  def initialize
    @news = []
  end

  def batman task
    puts "Batman: #{task} - done"
    yield "feed cat"
    @news << task
  end

end

cat_woman = lambda do |task| 
  puts "Cat Woman: #{task} - done" 
  # invoker.news << task
end

robin = lambda do |task| 
  puts "Robin: #{task} - done"
  # invoker.news << task
end


characters = SuperHeros.new
characters.batman("kick Joker's ass", &cat_woman)
characters.batman("break Bane's bones", &robin)

person Jikku Jose    schedule 07.11.2013    source источник


Ответы (2)


Вы можете использовать нечто похожее на оценку экземпляра с делегированием шаблон, используемый, например, в Savon gem:

def batman(task, &block)
  @original_self = eval('self', block.binding)
  puts "Batman: #{task} - done"
  instance_exec('feed cat', &block)
  @news << task
end

private

def method_missing(method, *args, &block)
  if @original_self
    @original_self.send(method, *args, &block)
  else
    super
  end
end

В этом подходе, когда вы вызываете метод (с неявным приемником) внутри блока, переданного в метод batman, он вызывается в контексте экземпляра SuperHeros. Если такого метода нет, вызов переходит (через method_missing) к исходному блоку self.

person Marek Lipka    schedule 07.11.2013
comment
Спасибо за ваше время, но не могли бы вы указать, как передавать параметры в блок при использовании в instance_eval()? - person Jikku Jose; 07.11.2013
comment
@JikkuJose, вы можете использовать instance_exec - посмотрите мой отредактированный пост. - person Marek Lipka; 07.11.2013
comment
О, круто, это работает! Я не использовал эту реализацию method_missing! И, честно говоря, тоже не понял, для чего этот original_self. (Это была моя модификация кода с вашим предложением: pastebin.com/ZdHsZ4Ck) - person Jikku Jose; 07.11.2013
comment
@JikkuJose Я использовал это на случай, если метод, который вы вызываете в блоке (с неявным приемником), не найден в методе экземпляра SuperHeros, тогда он делегируется self, оцениваемому из блока. Но, как вы уже заметили, это не всегда необходимо. Кажется, твоего решения достаточно. Пожалуйста, примите мой ответ, если вы нашли его полезным. - person Marek Lipka; 07.11.2013

Самый простой способ получить объект-приемник внутри блока — это присвоить объект переменной экземпляра.

Этот пример более наглядно иллюстрирует, как лямбда-выражения cat_woman и robin могут получить доступ к атрибутам объектов-получателей блоков:

class SuperHeros
  attr_accessor :news, :name, :current_task

  def initialize(a_name)
    @name = a_name
    @news = []
  end

  def batman(task)
    puts "Inside the method batman of #{name}: #{task} in progress ..."
    @current_task = task
    yield
    @news << task
  end

end

cat_woman = lambda do |extra_task|
  puts "cat_woman even #{extra_task} before doing #{@caller_obj.current_task}"
  puts "Cat Woman: #{@caller_obj.current_task} - done by #{@caller_obj.name}"
  # invoker.news << task
end

robin = lambda do |extra_task|
  puts "robin even #{extra_task} before doing #{@caller_obj.current_task}"
  puts "Robin: #{@caller_obj.current_task} - done by #{@caller_obj.name}"
end


character_1 = SuperHeros.new('batman_1')
(@caller_obj = character_1).batman("kick Joker's ass") { cat_woman['eats some burger'] }

puts

character_2 = SuperHeros.new('batman_2')
(@caller_obj = character_2).batman("break Bane's bones") { robin['drinks some beer'] }

Вывод будет:

Inside the method batman of batman_1: kick Joker's ass in progress ...
cat_woman even eats some burger before doing kick Joker's ass
Cat Woman: kick Joker's ass - done by batman_1

Inside the method batman of batman_2: break Bane's bones in progress ...
robin even drinks some beer before doing break Bane's bones
Robin: break Bane's bones - done by batman_2
person Châu Hồng Lĩnh    schedule 17.02.2017