Como faço para usar define_method para criar métodos de classe?

108

Isso é útil se você está tentando criar métodos de classe metaprogramaticamente:

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

Minha resposta a seguir ...

Chinasauro
fonte

Respostas:

196

Acho que em Ruby 1.9 você pode fazer isso:

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

A.loudly "my message"

# >> MY MESSAGE
Fguillen
fonte
4
tambémsingleton_class.define_method
Pyro
@Pyro Só para esclarecer, você poderia ir singleton_class.define_method :loudly do |message|etc.?
Joshua Pinter
25

Eu prefiro usar send para chamar define_method, e também gosto de criar um método metaclasse para acessar a metaclasse:

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
fonte
2
Obrigado! Definitivamente, existem maneiras de tornar isso mais agradável para você. Mas se você está trabalhando em um plug-in de código aberto, por exemplo, acho melhor não entupir o namespace metaclass, então é bom conhecer a abreviatura autônoma e fácil.
Chinasaur
Decidi ir com minha resposta original. Meu entendimento é que usar send () para acessar métodos privados está indo embora no Ruby 1.9, então isso não parece uma coisa boa de se usar. Além disso, se você estiver definindo mais de um método, instance_evaling um bloco é mais limpo.
Chinasaur
@Vincent Robert algum link que explicasse a magia do método metaclasse?
Amol Pujari
class << self; auto; fim; simplesmente reabre a classe de self (class << self) e, em seguida, retorna essa classe (self), retornando de fato a metaclasse de self.
Vincent Robert
10

Esta é a maneira mais simples em Ruby 1.8+:

class A
  class << self
    def method_name
      ...
    end
  end
end
Joseph Ravenwolfe
fonte
1
Eu realmente gosto deste. Pequeno, elegante, tem uma boa leitura e é portátil. Claro, você pode perguntar o que estou fazendo usando ruby ​​1.8 em 2013 ...
A Fader Darkly
8

Derivado de: Jay e Why , que também fornecem maneiras de tornar isso mais bonito.

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

Atualização : da contribuição da VR abaixo; um método mais conciso (contanto que você defina apenas um método desta forma) que ainda é autônomo:

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

mas note que usar send () para acessar métodos privados como define_method () não é necessariamente uma boa ideia (meu entendimento é que isso vai desaparecer no Ruby 1.9).

Chinasauro
fonte
Uma alternativa melhor (?) Pode ser colocar as coisas em um módulo e, em seguida, fazer com que seu create_class_method estenda o módulo para a classe ??? Veja: blog.jayfields.com/2008/07/ruby-underuse-of-modules.html
Chinasaur
6

Para ser usado no Rails se você quiser definir métodos de classe dinamicamente a partir de preocupação:

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

Você também pode fazer algo assim sem 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
fonte