Comment passez-vous des arguments à define_method?

155

Je voudrais passer un ou plusieurs arguments à une méthode définie à l'aide de define_method, comment ferais-je cela?

Sixty4Bit
la source

Réponses:

198

Le bloc que vous passez à define_method peut inclure certains paramètres. C'est ainsi que votre méthode définie accepte les arguments. Lorsque vous définissez une méthode, vous ne faites que surnommer le bloc et en garder une référence dans la classe. Les paramètres sont fournis avec le bloc. Alors:

define_method(:say_hi) { |other| puts "Hi, " + other }
Kevin Conner
la source
Eh bien, c'est juste une chose pure et pure. Beau travail, Kevin Costner.
Darth Egregious
90

... et si vous voulez des paramètres optionnels

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

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

... autant d'arguments que vous le souhaitez

 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']

...combinaison 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]

... tous

 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

Mettre à jour

Ruby 2.0 a introduit le double splat **(deux étoiles) qui ( je cite ) fait:

Ruby 2.0 a introduit des arguments de mots-clés et ** agit comme *, mais pour les arguments de mots-clés. Il renvoie un Hash avec des paires clé / valeur.

... et bien sûr, vous pouvez également l'utiliser dans la méthode define :)

 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}

Exemple d'attributs nommés:

 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}

J'essayais de créer un exemple avec un argument de mot-clé, un splat et un double splat tout en un:

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

ou

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

... mais cela ne fonctionnera pas, il semble qu'il y ait une limitation. Quand vous y réfléchissez, cela a du sens car l'opérateur splat "capture tous les arguments restants" et double splat "capture tous les arguments mot-clé restants", donc les mélanger casserait la logique attendue. (Je n'ai aucune référence pour prouver ce point doh!)

mise à jour 2018 août:

Article de synthèse: https://blog.eq8.eu/til/metaprogramming-ruby-examples.html

équivalent8
la source
Intéressant - spécialement le 4ème bloc: il a fonctionné sur 1.8.7! Le premier bloc ne fonctionnait pas dans la version 1.8.7, et le second bloc a une faute de frappe (devrait être à la a.foo 1place de foo 1). Merci!
Sony Santos
1
merci pour vos commentaires, une faute de frappe a été corrigée, ... Sur ruby ​​1.9.3 et 1.9.2, tous les exemples fonctionnent et je suis sûr que sur 1.9.1 aussi (mais je n'ai pas essayé)
équivalent8
J'ai combiné cette réponse avec la réponse acceptée sur stackoverflow.com/questions/4470108/… pour comprendre comment écraser (ne pas remplacer) une méthode au moment de l'exécution qui prend des arguments optionnels et un bloc et toujours pouvoir appeler la méthode d'origine avec les args et bloquer. Ah, rubis. Plus précisément, j'avais besoin d'écraser Savon :: Client.request dans mon environnement de développement pour un seul appel d'API à un hôte auquel je ne peux accéder qu'en production. À votre santé!
pduey
59

En plus de la réponse de Kevin Conner: les arguments de bloc ne prennent pas en charge la même sémantique que les arguments de méthode. Vous ne pouvez pas définir d'arguments par défaut ou bloquer des arguments.

Ceci n'est corrigé que dans Ruby 1.9 avec la nouvelle syntaxe alternative "stabby lambda" qui supporte la sémantique complète des arguments de méthode.

Exemple:

# 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
la source
3
En fait, je crois que les arguments de bloc sur define_method prennent en charge splat, ce qui peut fournir un moyen circulaire pour définir également les arguments par défaut.
Chinasaur du
1
Chinasaur a raison sur les arguments de bloc autorisant les splats. J'ai confirmé cela dans Ruby 1.8.7 et 1.9.1.
Peter Wagenet
Merci, j'ai oublié ça. Corrigé maintenant.
Jörg W Mittag