¿Cómo pasas argumentos a define_method?

155

Me gustaría pasar un argumento (s) a un método que se define utilizando define_method, ¿cómo lo haría?

Sixty4Bit
fuente

Respuestas:

198

El bloque que pasa a define_method puede incluir algunos parámetros. Así es como su método definido acepta argumentos. Cuando define un método, en realidad solo está apodando el bloque y manteniendo una referencia a él en la clase. Los parámetros vienen con el bloque. Entonces:

define_method(:say_hi) { |other| puts "Hi, " + other }
Kevin Conner
fuente
Bueno, eso es solo una pura beatitud sin adulterar. Buen trabajo, Kevin Costner.
Darth Egregious
90

... y si quieres parámetros opcionales

 class Bar
   define_method(:foo) do |arg=nil|                  
     arg                                                                                          
   end   
 end

 a = Bar.new
 a.foo
 #=> nil
 a.foo 1
 # => 1

... tantos argumentos como quieras

 class Bar
   define_method(:foo) do |*arg|                  
     arg                                                                                          
   end   
 end

 a = Bar.new
 a.foo
 #=> []
 a.foo 1
 # => [1]
 a.foo 1, 2 , 'AAA'
 # => [1, 2, 'AAA']

...combinación de

 class Bar
   define_method(:foo) do |bubla,*arg|
     p bubla                  
     p arg                                                                                          
   end   
 end

 a = Bar.new
 a.foo
 #=> wrong number of arguments (0 for 1)
 a.foo 1
 # 1
 # []

 a.foo 1, 2 ,3 ,4
 # 1
 # [2,3,4]

... todos ellos

 class Bar
   define_method(:foo) do |variable1, variable2,*arg, &block|  
     p  variable1     
     p  variable2
     p  arg
     p  block.inspect                                                                              
   end   
 end
 a = Bar.new      
 a.foo :one, 'two', :three, 4, 5 do
   'six'
 end

Actualizar

Ruby 2.0 introdujo doble splat **(dos estrellas) que ( cito ) hace:

Ruby 2.0 introdujo argumentos de palabras clave, y ** actúa como *, pero para argumentos de palabras clave. Devuelve un hash con pares clave / valor.

... y, por supuesto, también puedes usarlo en el método de definición :)

 class Bar 
   define_method(:foo) do |variable1, variable2,*arg,**options, &block|
     p  variable1
     p  variable2
     p  arg
     p  options
     p  block.inspect
   end 
 end 
 a = Bar.new
 a.foo :one, 'two', :three, 4, 5, ruby: 'is awesome', foo: :bar do
   'six'
 end
# :one
# "two"
# [:three, 4, 5]
# {:ruby=>"is awesome", :foo=>:bar}

Ejemplo de atributos con nombre:

 class Bar
   define_method(:foo) do |variable1, color: 'blue', **other_options, &block|
     p  variable1
     p  color
     p  other_options
     p  block.inspect
   end
 end
 a = Bar.new
 a.foo :one, color: 'red', ruby: 'is awesome', foo: :bar do
   'six'
 end
# :one
# "red"
# {:ruby=>"is awesome", :foo=>:bar}

Intenté crear un ejemplo con argumento de palabra clave, splat y splat doble, todo en uno:

 define_method(:foo) do |variable1, variable2,*arg, i_will_not: 'work', **options, &block|
    # ...

o

 define_method(:foo) do |variable1, variable2, i_will_not: 'work', *arg, **options, &block|
    # ...

... pero esto no funcionará, parece que hay una limitación. Cuando lo piensa, tiene sentido ya que el operador splat está "capturando todos los argumentos restantes" y el splat doble está "capturando todos los argumentos de palabras clave restantes", por lo tanto, mezclarlos rompería la lógica esperada. (¡No tengo ninguna referencia para probar este punto, doh!)

actualización 2018 agosto:

Artículo resumen: https://blog.eq8.eu/til/metaprogramming-ruby-examples.html

equivalente8
fuente
Interesante, especialmente el 4to bloque: ¡funcionó en 1.8.7! El primer bloque no funcionó en 1.8.7, y el segundo bloque tiene un error tipográfico (debería ser en a.foo 1lugar de foo 1). ¡Gracias!
Sony Santos
1
gracias por sus comentarios, se corrigió el error tipográfico, ... En ruby ​​1.9.3 y 1.9.2 todos los ejemplos funcionan y estoy seguro de que también en 1.9.1 (pero no lo intenté)
equivalente8
Combiné esta respuesta con la respuesta aceptada en stackoverflow.com/questions/4470108/… para descubrir cómo sobrescribir (no anular) un método en tiempo de ejecución que requiere args y un bloque opcionales y aún así poder llamar al método original con los args y bloque. Ah, rubí. Específicamente, necesitaba sobrescribir Savon :: Client.request en mi entorno de desarrollo para una sola llamada API a un host al que solo puedo acceder en producción. ¡Salud!
pduey
59

Además de la respuesta de Kevin Conner: los argumentos de bloque no admiten la misma semántica que los argumentos de método. No puede definir argumentos predeterminados o argumentos de bloque.

Esto solo se soluciona en Ruby 1.9 con la nueva sintaxis alternativa "stabby lambda" que admite la semántica de argumentos de método completo.

Ejemplo:

# Works
def meth(default = :foo, *splat, &block) puts 'Bar'; end

# Doesn't work
define_method :meth { |default = :foo, *splat, &block| puts 'Bar' }

# This works in Ruby 1.9 (modulo typos, I don't actually have it installed)
define_method :meth, ->(default = :foo, *splat, &block) { puts 'Bar' }
Jörg W Mittag
fuente
3
En realidad, creo que los argumentos de bloque en define_method admiten splat, que puede proporcionar una forma redonda de definir argumentos predeterminados también.
Chinasaur
1
Chinasaur tiene razón sobre los argumentos de bloque que permiten símbolos. He confirmado esto en Ruby 1.8.7 y 1.9.1.
Peter Wagenet el
Gracias, me olvidé de esto. Corregido ahora.
Jörg W Mittag el