web-dev-qa-db-fra.com

Extension d'un module Ruby dans un autre module, y compris les méthodes de module

Chaque fois que j'essaie d'étendre un module Ruby, je perds les méthodes du module. Ni inclure ni étendre ne le fera. Considérons l'extrait de code:

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

module B 
  include A
end

B.say_hi  #undefined_method

Que B inclue ou étende A, say_hi ne sera pas défini. 

Y a-t-il un moyen d'accomplir quelque chose comme ça?

31
Jonathan Martin

Si vous êtes l'auteur de module A et que vous en avez fréquemment besoin, vous pouvez ré-écrire A comme suit:

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" 
25
Phrogz

Je ne pense pas qu'il existe un moyen simple de le faire.

Donc, voici une manière complexe:

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

Vous pouvez le mettre dans une méthode d'assistance comme celle-ci:

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
9
Jeremy Ruten

Utilisez 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"
3
horseyguy

Johnathan, Je ne suis pas sûr que vous vous posiez encore des questions à ce sujet, mais il existe deux manières différentes d'utiliser des modules en Ruby. A.) vous utilisez des modules sous leur forme autonome Base :: Tree.entity (params) directement dans votre code, ou B.) vous utilisez des modules en tant que méthodes mixins ou auxiliaires. 

A. Vous permettra d'utiliser des modules en tant que modèle d'espace de noms. C'est bon pour les grands projets où il y a une chance pour des conflits de nom de méthode

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

Vous pouvez maintenant l'utiliser pour créer une sorte d'arborescence dans votre code, sans avoir à instancier une nouvelle classe pour chaque appel à Base :: Tree.entity.

Une autre façon de faire en utilisant Namespace-ing est de classe par classe. 

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

Nous avons maintenant le potentiel de chevauchement dans nos classes. Nous voulons savoir que lorsque nous créons une classe Actor, il s'agit de la bonne classe, c'est donc ici que les espaces de noms sont utiles. 

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

B. Mix-Ins Ce sont vos amis. Utilisez-les chaque fois que vous pensez devoir faire quelque chose plus d'une fois dans votre code.

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

Maintenant, chaque fois que vous avez besoin de formater une chaîne xml en tant que hachage, ou de supprimer les espaces blancs de fin dans votre futur code, mélangez-les simplement. 

module Fun
  class Ruby
    include Friendly::Formatter

    attr_accessor :string

    def initialize(params={})
    end

  end
end

Vous pouvez maintenant formater la chaîne dans votre classe. 

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)

J'espère que c'est une bonne explication. Les points soulevés ci-dessus sont de bons exemples de moyens d'étendre les classes, mais avec les modules, le plus difficile est de savoir quand utiliser le mot-clé self . Il fait référence à la portée de l'objet dans la hiérarchie des objets de Ruby. Donc, si vous voulez utiliser un module en tant que mix-in et ne voulez rien déclarer de singleton, n'utilisez pas le mot-clé self. Cependant, si vous souhaitez conserver l'état dans l'objet, utilisez simplement une classe et dans les modules que vous voulez.

2
newfront

Je n'aime pas que tout le monde utilise self.included. J'ai une solution plus 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
0
puchu