Могу ли я динамически определить метод Ruby, который принимает блок? - PullRequest
8 голосов
/ 27 сентября 2011

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

Я хочу динамически определить метод, который принимает как необязательные параметры, так и блок. В Ruby 1.9 это легко, потому что теперь разрешено передавать блок в блок.

К сожалению, Ruby 1.8 не позволяет этого, поэтому следующее не будет работать:

#Ruby 1.8
class X
  define_method :foo do |bar, &baz|
    puts bar
    baz.call if block_given?
  end
end

x = X.new
x.foo("foo") { puts "called!"} #=> LocalJumpError: no block given

Замена явного block.call на yield также не решает проблему.
Обновление до Ruby 1.9, к сожалению, не вариант для меня. Это неразрешимая проблема или есть способ ее обойти?

Ответы [ 2 ]

6 голосов
/ 27 сентября 2011

Это работает с Ruby 1.8.7, но не с 1.8.6:

class X
  define_method(:foo) do |bar, &baz|
    puts bar
    baz.call if baz
  end
end

Тестирование с:

X.new.foo("No block")
X.new.foo("With block") { puts "  In the block!"}
p = proc {puts "  In the proc!"}
X.new.foo("With proc", &p)

дает:

No block
With block
  In the block!
With proc
  In the proc!

(с 1.8.6 это дает syntax error, unexpected tAMPER, expecting '|'.)

Если вам нужны дополнительные аргументы, а также блок, вы можете попробовать что-то вроде этого:

class X
  define_method(:foo) do |*args, &baz|
    if args[0]
      bar = args[0]
    else
      bar = "default"
    end
    puts bar
    baz.call if baz
  end
end

тестирование с:

X.new.foo
X.new.foo { puts "  No arg but block"}

дает:

default
default
  No arg but block
4 голосов
/ 27 сентября 2011

Что вы можете сделать, это использовать class_eval со строкой вместо define_method. Недостатком этого (кроме того, что он не такой элегантный) является то, что вы теряете лексическую область видимости. Но это часто не нужно.

...