¿Cómo uso define_method para crear métodos de clase?

108

Esto es útil si está intentando crear métodos de clase metaprogramáticamente:

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

Mi respuesta a seguir ...

Chinasaurio
fuente

Respuestas:

196

Creo que en Ruby 1.9 puedes hacer esto:

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

A.loudly "my message"

# >> MY MESSAGE
fguillen
fuente
4
tambiénsingleton_class.define_method
Pyro
@Pyro Solo para aclarar, ¿irías, singleton_class.define_method :loudly do |message|etc.?
Joshua Pinter
25

Prefiero usar enviar para llamar a define_method, y también me gusta crear un método de metaclase para acceder a la metaclase:

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
fuente
2
¡Gracias! Definitivamente hay formas de hacer esto mejor para ti. Pero si está trabajando en un complemento de código abierto, por ejemplo, creo que es mejor no obstruir el espacio de nombres metaclass, por lo que es bueno saber la abreviatura fácil e independiente.
Chinasaur
Decidí seguir mi respuesta original. Tengo entendido que usar send () para acceder a métodos privados si desaparece en Ruby 1.9, por lo que no parecía una buena opción. Además, si está definiendo más de un método, instancia_evaluar un bloque es más limpio.
Chinasaur
@ Vincent Robert ¿Algún enlace que explique la magia del método de metaclase?
Amol Pujari
class << self; yo; final; simplemente reabre la clase de self (clase << self) y luego devuelve esa clase (self), por lo que en realidad devuelve la metaclase de self.
Vincent Robert
10

Esta es la forma más sencilla en Ruby 1.8+:

class A
  class << self
    def method_name
      ...
    end
  end
end
Joseph Ravenwolfe
fuente
1
Realmente me gusta esta. Pequeño, ordenado, se lee bien y es portátil. Por supuesto, podría preguntar qué estoy haciendo usando ruby ​​1.8 en 2013 ...
A Fader Darkly
8

Derivado de: Jay and Why , quienes también brindan formas de hacer esto más bonito.

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

Actualización : de la contribución de VR a continuación; un método más conciso (siempre que solo esté definiendo un método de esta manera) que aún es independiente:

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

pero tenga en cuenta que usar send () para acceder a métodos privados como define_method () no es necesariamente una buena idea (tengo entendido que desaparecerá en Ruby 1.9).

Chinasaurio
fuente
¿Mejor (?) Alternativa puede ser poner cosas en un módulo y luego hacer que su create_class_method extienda el módulo a la clase ??? Ver: blog.jayfields.com/2008/07/ruby-underuse-of-modules.html
Chinasaur
6

Para ser utilizado en Rails si desea definir métodos de clase dinámicamente por preocupación:

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
fuente
-1

También puede hacer algo como esto sin depender de 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
fuente