2011-01-11 23 views
30

Cada vez que intento extender un módulo Ruby, pierdo los métodos del módulo. Ni incluir ni extender hará esto. Considere el fragmento:Extensión de un módulo Ruby en otro módulo, incluidos los métodos del módulo

module A 
    def self.say_hi 
    puts "hi" 
    end 
end 

module B 
    include A 
end 

B.say_hi #undefined_method 

Si B incluye o amplía A, say_hi no se definirá.

¿Hay alguna manera de lograr algo como esto?

+1

¿Puede proporcionar un caso concreto en el que desee hacer esto? La herencia de módulos es lo que realmente deseas, y esto solo se admite específicamente e intencionalmente para Clases en Ruby. ¿Por qué tampoco a) siempre incluir B y A en el mismo objeto, o b) al incluir B también incluir A? – Phrogz

+0

La razón por la que quiero esto es un poco complejo. El módulo en cuestión se utiliza para crear muchas clases de ActiveRecord :: Base a partir de un documento XML que describe las relaciones y las tablas de base de datos. Quiero poder hacer esto varias veces diferentes. El código del módulo es el mismo para cada uno, pero para que los espacios de nombres y las clases relacionadas no entren en conflicto, deben ser módulos distintos. Tampoco quiero tener que instanciar una clase solo para incluir el módulo cada vez que hago esto, descartando y métodos de instancia. –

Respuesta

9

No creo que haya una forma sencilla de hacerlo.

Así que aquí es una forma compleja:

module B 
    class << self 
    A.singleton_methods.each do |m| 
     define_method m, A.method(m).to_proc 
    end 
    end 
end 

Usted puede ponerlo en un método de ayuda como esto:

class Module 
    def include_module_methods(mod) 
    mod.singleton_methods.each do |m| 
     (class << self; self; end).send :define_method, m, mod.method(m).to_proc 
    end 
    end 
end 

module B 
    include_module_methods A 
end 
+1

¡Brillante! Me llevó mucho tiempo descubrir cómo evitar este error: 'TypeError: método singleton llamado para un objeto diferente'. '# to_proc' resolvió esto con facilidad. –

24

Si usted es el autor de module A y con frecuencia se necesita esto, puede re-editar un modo:

module A 
    module ClassMethods 
    def say_hi 
     puts "hi" 
    end 
    end 
    extend ClassMethods 
    def self.included(other) 
    other.extend(ClassMethods) 
    end 
end 

module B 
    include A 
end 

A.say_hi #=> "hi" 
B.say_hi #=> "hi" 
+0

Intenté esto, y no funciona para mi caso. Como mencioné en mi comentario (creo que después de su respuesta) el propósito de esto es usar una función que crea clases dentro del espacio del módulo usando const_set y Class.new ... ¡Pero! Si lo hago de esta manera e invoco B.create_classes, encuentro que las clases no solo están definidas en B, sino también en A. –

+0

@JonathanMartin Deberás actualizar tu pregunta con un caso de prueba reproducible, entonces. Tal vez ActiveRecord está haciendo un poco de magia alocada y enviando spam a tus espacios, pero debería funcionar. – Phrogz

+0

Esto es genial. Gracias. –

3

uso include_complete

gem install include_complete

module A 
    def self.say_hi 
    puts "hi" 
    end 
end 

module B 
    include_complete A 
end 

B.say_hi #=> "hi" 
+0

Dijiste que tu cth de gema solo funciona con ruby ​​1.9.2. =) – puchu

+0

@puchu mira el estado dejé esta respuesta - 2011 – horseyguy

+0

16 de julio de 2015. Y su gema todavía no está funcionando con ruby ​​2.2 y 2.3 hoy. – puchu

2

Johnathan, no estoy seguro de si todavía se está preguntando acerca de esto, pero hay dos formas diferentes de utilizar módulos de rubí. A.) usa módulos en su forma autónoma Base :: Tree.entity (params) directamente en su código, o B.) usa módulos como mixins o métodos auxiliares.

A. Le permitirá utilizar módulos como un patrón de espacio de nombres. Esto es bueno para los proyectos más grandes, donde existe la posibilidad de que el nombre del método en conflicto

module Base 
    module Tree 
    def self.entity(params={},&block) 
     # some great code goes here 
    end 
    end 
end 

Ahora se puede usar esto para crear algún tipo de estructura de árbol en su código, sin tener que crear una instancia de una nueva clase para cada llamada a Base :: Tree.entity.

Otra forma de hacer Namespace-ing es en una clase por clase.

module Session 
    module Live 
    class Actor 
     attr_accessor :type, :uuid, :name, :status 
     def initialize(params={},&block) 
     # check params, insert init values for vars..etc 
     # save your callback as a class variable, and use it sometime later 
     @block = block 
     end 

     def hit_rock_bottom 
     end 

     def has_hit_rock_bottom? 
     end 

     ... 
    end 
end 
class Actor 
    attr_accessor :id,:scope,:callback 
    def initialize(params={},&block) 
    self.callback = block if block_given? 
    end 

    def respond 
    if self.callback.is_a? Proc 
     # do some real crazy things... 
    end 
    end 
end 
end 

Ahora tenemos la posibilidad de superposición en nuestras clases. Queremos saber que cuando creamos una clase Actor es la clase correcta, así que aquí es donde los espacios de nombres son útiles.

Session::Live::Actor.new(params) do |res|... 
Session::Actor.new(params) 

B. Mezclar-Ins Estos son tus amigos. Úselos cada vez que crea que tendrá que hacer algo más de una vez en su código.

module Friendly 
    module Formatter 
    def to_hash(xmlstring) 
     #parsing methods 
     return hash 
    end 

    def remove_trailing_whitespace(string,&block) 
     # remove trailing white space from that idiot who pasted from textmate 
    end 
    end 
end 

Ahora, cada vez que necesita para dar formato a un xmlString como un hash, o eliminar espacios en blanco en cualquiera de su futuro código, simplemente mezclar en.

module Fun 
    class Ruby 
    include Friendly::Formatter 

    attr_accessor :string 

    def initialize(params={}) 
    end 

    end 
end 

Ahora puede dar formato a la cadena en su clase.

fun_ruby = Fun::Ruby.new(params) 
fun_ruby.string = "<xml><why><do>most</do><people></people><use>this</use><it>sucks</it></why></xml>" 
fun_ruby_hash = fun_ruby.to_hash(fun_ruby.string) 

Espero que esta sea una buena explicación.Los puntos planteados anteriormente son buenos ejemplos de formas de extender clases, pero con los módulos, la parte difícil es cuándo usar la palabra clave. Se refiere al alcance del objeto dentro de la jerarquía de objetos de ruby. Entonces, si desea usar un módulo como mezcla y no desea declarar nada solo, no use la palabra clave auto, sin embargo, si desea mantener el estado dentro del objeto, solo use una clase y mezcle en los módulos que quieras.

0

No me gusta que todos usen self.included. Tengo una solución más simple:

module A 
    module ClassMethods 
    def a 
     'a1' 
    end 
    end 
    def a 
    'a2' 
    end 
end 

module B 
    include A 

    module ClassMethods 
    include A::ClassMethods 
    def b 
     'b1' 
    end 
    end 
    def b 
    'b2' 
    end 
end 

class C 
    include B 
    extend B::ClassMethods 
end 

class D < C; end 

puts D.a 
puts D.b 
puts D.new.a 
puts D.new.b 
Cuestiones relacionadas