web-dev-qa-db-fra.com

Ruby: define_method vs def

En tant qu'exercice de programmation, j'ai écrit un extrait Ruby qui crée une classe, instancie deux objets de cette classe, monkeypatches un objet et s'appuie sur method_missing pour monkeypatch l'autre.

Voici l'affaire. Cela fonctionne comme prévu:

class Monkey

  def chatter
    puts "I am a chattering monkey!"
  end

  def method_missing(m)
    puts "No #{m}, so I'll make one..."
    def screech
      puts "This is the new screech."
    end
  end
end

m1 = Monkey.new
m2 = Monkey.new

m1.chatter
m2.chatter

def m1.screech
  puts "Aaaaaargh!"
end

m1.screech
m2.screech
m2.screech
m1.screech
m2.screech

Vous remarquerez que j'ai un paramètre pour method_missing. J'ai fait cela parce que j'espérais utiliser define_method pour créer dynamiquement des méthodes manquantes avec le nom approprié. Mais ça ne marche pas. En fait, même en utilisant define_method avec un nom statique comme celui-ci:

def method_missing(m)
  puts "No #{m}, so I'll make one..."
  define_method(:screech) do
    puts "This is the new screech."
  end
end

Se termine par le résultat suivant:

ArgumentError: wrong number of arguments (2 for 1)

method method_missing   in untitled document at line 9
method method_missing   in untitled document at line 9
at top level    in untitled document at line 26
Program exited.

Ce qui rend le message d'erreur plus déroutant, c'est que je n'ai qu'un seul argument pour method_missing...

65
gauth

define_method est une méthode (privée) de l'objet Class. Vous l'appelez depuis un instance. Il n'y a pas de méthode d'instance appelée define_method, donc il revient à votre method_missing, cette fois avec :define_method (le nom de la méthode manquante) et :screech (le seul argument que vous avez transmis à define_method).

Essayez ceci à la place (pour définir la nouvelle méthode sur tous les objets Monkey):

def method_missing(m)
    puts "No #{m}, so I'll make one..."
    self.class.send(:define_method, :screech) do
      puts "This is the new screech."
    end
end

Ou ceci (pour le définir uniquement sur l'objet auquel il est appelé, en utilisant la "classe propre" de l'objet):

def method_missing(m)
    puts "No #{m}, so I'll make one..."
    class << self
      define_method(:screech) do
        puts "This is the new screech."
      end
    end
end
136
Avdi

self.class.define_method (: screech) ne fonctionne pas, car define_method est une méthode privée, vous pouvez le faire

class << self
    public :define_method
end
def method_missing(m)
puts "No #{m}, so I'll make one..."
Monkey.define_method(:screech) do
  puts "This is the new screech."
end
4
clf
def method_missing(m)
    self.class.class_exec do
       define_method(:screech) {puts "This is the new screech."}
    end 
end

la méthode screech sera disponible pour tous les objets Monkey.

4
Andrew