Как переслать блок в метод, который генерирует методы - PullRequest
0 голосов
/ 19 декабря 2018

В моем текущем проекте у меня есть следующий шаблон повторения в некоторых классах:

class MyClass

  def method1(pars1, ...)
    preamble

    # implementation method1

    rescue StandardError => e
      recovery
  end

  def method2(pars2, ...)
    preamble

    # implementation method2

    rescue StandardError => e
      recovery
  end

  # more methods with the same pattern
end

Итак, я думал о том, как высушить этот шаблон повторения.Моя цель - получить что-то вроде этого:

class MyClass

  define_operation :method1, pars1, ... do
    # implementation method1
  end

  define_operation :method2, pars2, ... do
    # implementation method2
  end

  # more methods with the same pattern but generated with define_wrapper_method
  end

Я пытался реализовать своего рода метагенератор, но у меня были проблемы с пересылкой блока, который получит генератор.Это более или менее то, что я пробовал:

def define_operation(op_name, *pars, &block)
  define_method(op_name.to_s) do |*pars|
    preamble
    yield # how can I do here for getting the block? <-----
    rescue StandardError => e
      recovery
  end
end

К сожалению, я не могу найти способ переадресации block к методу define_method.Кроме того, вполне возможно, что параметры, чей номер является переменным, неправильно передаются в define_method.

Буду признателен за любую подсказку, помощь, предложение.

Ответы [ 2 ]

0 голосов
/ 20 декабря 2018

Если я правильно понимаю, вы ищете что-то вроде:

class Operation
  def self.op(name,&block)
    define_method(name) do |*args|
      op_wrap(block.arity,*args,&block)
    end
  end

  def op_wrap(arity=0,*args)
    if arity == args.size || (arrity < 0  && args.size >= arity.abs - 1) 
      begin
        preamble 
        yield *args
      rescue StandardError => e
        recovery 
      end  
    else 
      raise ArgumentError, "wrong number of arguments (given #{args.size}, expected #{arity < 0 ? (arity.abs - 1).to_s.+('+') : arity })"
    end
  end

  def preamble
    puts __method__
  end

  def recovery
    puts __method__
  end
end

Таким образом, ваше использование будет

class MyClass < Operation

  op :thing1 do |a,b,c| 
    puts "I got #{a},#{b},#{c}"
  end

  op :thing2 do |a,b|
    raise StandardError
  end

  def thing3
    thing1(1,2,3) 
  end
end

Кроме того, это предлагает вам оба варианта, представленные, как вы все еще могли бы сделать

def thing4(m1,m2,m3) 
   @m1 = m1
   op_wrap(1,'inside_wrapper') do |str| 
     # no need to yield because the m1,m2,m3 are in scope 
     # but you could yield other arguments
     puts "#{str} in #{__method__}" 
   end
end

Позволяет вам предварительно обработать аргументы и решить, что передать блоку

Примеры

m = MyClass.new

m.thing1(4,5,6)
# preamble
# I got 4,5,6
#=> nil
m.thing2('A','B')
# preamble
# recovery
#=> nil
m.thing3  
# preamble
# I got 1,2,3
#=> nil
m.thing1(12)
#=> #<ArgumentError: wrong number of arguments (given 1, expected 3)>
0 голосов
/ 19 декабря 2018

Вам не нужно метапрограммирование для достижения этой цели.Просто добавьте новый метод, который оборачивает общую логику, как показано ниже:

class MyClass

  def method1(param1)
    run_with_recovery(param1) do |param1|
       # implementation method1
    end
  end

  def method2(param1, param2)
    run_with_recovery(param1, param2) do |param1, param2|
       # implementation method2
    end
  end

  private

  def run_with_recovery(*params)
    preamble
    yield(*params)
    rescue StandardError => e
      recovery
  end
end

Проверьте его здесь: http://rubyfiddle.com/riddles/4b6e2


Если вы действительно хотите выполнить метапрограммирование, это будет работать:

class MyClass

  def self.define_operation(op_name)
    define_method(op_name.to_s) do |*args|
      begin
        puts "preamble"
        yield(args)
      rescue StandardError => e
        puts "recovery"
      end
    end
  end

  define_operation :method1 do |param1|
    puts param1
  end

  define_operation :method2 do |param1, param2|
    puts param1
    puts param2
  end

end

MyClass.new.method1("hi")
MyClass.new.method2("hi", "there")

Проверьте это здесь: http://rubyfiddle.com/riddles/81b9d/2

...