Comment utiliser define_method pour créer des méthodes de classe?

108

Ceci est utile si vous essayez de créer des méthodes de classe de manière métaprogrammée:

def self.create_methods(method_name)
    # To create instance methods:
    define_method method_name do
      ...
    end

    # To create class methods that refer to the args on create_methods:
    ???
end

Ma réponse à suivre ...

Chinasaur
la source

Réponses:

196

Je pense que dans Ruby 1.9, vous pouvez faire ceci:

class A
  define_singleton_method :loudly do |message|
    puts message.upcase
  end
end

A.loudly "my message"

# >> MY MESSAGE
fguillen
la source
4
aussisingleton_class.define_method
Pyro
@Pyro Juste pour clarifier, voudriez-vous simplement aller, singleton_class.define_method :loudly do |message|etc.?
Joshua Pinter
25

Je préfère utiliser send to call define_method, et j'aime aussi créer une méthode de métaclasse pour accéder à la métaclasse:

class Object
  def metaclass
    class << self
      self
    end
  end
end

class MyClass
  # Defines MyClass.my_method
  self.metaclass.send(:define_method, :my_method) do
    ...
  end
end
Vincent Robert
la source
2
Merci! Il existe certainement des moyens de rendre cela plus agréable pour vous-même. Mais si vous travaillez sur un plugin open source, par exemple, je pense qu'il est plus agréable de ne pas obstruer l'espace de noms metaclass, donc c'est bien de connaître le raccourci simple et autonome.
Chinasaur
J'ai décidé d'aller avec ma réponse originale. Je crois comprendre que l'utilisation de send () pour accéder aux méthodes privées si vous partez dans Ruby 1.9, donc cela ne semble pas être une bonne chose à utiliser. De plus, si vous définissez plus d'une méthode, l'instance_evaling d'un bloc est plus propre.
Chinasaur
@Vincent Robert un lien qui expliquerait la magie de la méthode métaclasse?
Amol Pujari
classe << soi; soi; fin; rouvre simplement la classe de self (class << self) et retourne ensuite cette classe (self) pour renvoyer en fait la métaclasse de self.
Vincent Robert
10

C'est le moyen le plus simple dans Ruby 1.8+:

class A
  class << self
    def method_name
      ...
    end
  end
end
Joseph Ravenwolfe
la source
1
J'aime vraiment celle-ci. Petit, soigné, se lit bien et il est portable. Bien sûr, vous pouvez me demander ce que je fais avec ruby ​​1.8 en 2013 ...
A Fader Darkly
8

Dérivé de: Jay et Why , qui fournissent également des moyens de rendre cela plus joli.

self.create_class_method(method_name)
  (class << self; self; end).instance_eval do
    define_method method_name do
      ...
    end
  end
end

Mise à jour : à partir de la contribution de VR ci-dessous; une méthode plus concise (tant que vous ne définissez qu'une seule méthode de cette façon) qui est toujours autonome:

self.create_class_method(method_name)
  (class << self; self; end).send(:define_method, method_name) do
    ...
  end
end

mais notez que l'utilisation de send () pour accéder à des méthodes privées comme define_method () n'est pas nécessairement une bonne idée (je crois comprendre que cela disparaîtra dans Ruby 1.9).

Chinasaur
la source
La meilleure alternative (?) Peut être de mettre des choses dans un module et ensuite de faire étendre le module create_class_method à la classe ??? Voir: blog.jayfields.com/2008/07/ruby-underuse-of-modules.html
Chinasaur
6

A utiliser dans Rails si vous souhaitez définir dynamiquement des méthodes de classe par souci:

module Concerns::Testable
  extend ActiveSupport::Concern

  included do 
    singleton_class.instance_eval do
      define_method(:test) do
        puts 'test'
      end
    end
  end
end
Artur Beljajev
la source
-1

Vous pouvez également faire quelque chose comme ça sans vous fier à define_method:

A.class_eval do
  def self.class_method_name(param)
    puts param
  end
end

A.class_method_name("hello") # outputs "hello" and returns nil
Bijan
la source