web-dev-qa-db-fra.com

Puis-je appeler une méthode d'instance sur un module Ruby sans l'inclure?)

Contexte:

J'ai un module qui déclare un certain nombre de méthodes d'instance

module UsefulThings
  def get_file; ...
  def delete_file; ...

  def format_text(x); ...
end

Et je veux appeler certaines de ces méthodes depuis une classe. Comment vous faites normalement ceci dans Ruby est comme ceci:

class UsefulWorker
  include UsefulThings

  def do_work
    format_text("abc")
    ...
  end
end

Problème

include UsefulThings introduit dans tout des méthodes de UsefulThings. Dans ce cas, je ne veux que format_text et explicitement ne veulent pas get_file et delete_file.

Je peux voir plusieurs solutions possibles à cela:

  1. En quelque sorte, invoquez la méthode directement sur le module sans l’inclure nulle part
    • Je ne sais pas comment/si cela peut être fait. (D'où cette question)
  2. En quelque sorte, inclure Usefulthings et n’apporter que certaines de ses méthodes
    • Je ne sais pas non plus comment/si cela peut être fait
  3. Créez une classe proxy, incluez UsefulThings, puis déléguez format_text à cette instance de proxy
    • Cela fonctionnerait, mais les classes proxy anonymes sont un hack. Beurk.
  4. Divisez le module en 2 ou plusieurs modules plus petits
    • Cela fonctionnerait aussi, et est probablement la meilleure solution à laquelle je puisse penser, mais je préférerais l'éviter car je finirais avec une prolifération de dizaines et de dizaines de modules - gérer cela serait fastidieux.

Pourquoi y a-t-il beaucoup de fonctions non liées dans un seul module? Il s’agit de ApplicationHelper d’une application Rails, que notre équipe a de facto choisie comme dépotoir pour tout ce qui n’est pas suffisamment spécifique pour appartenir à un autre endroit. Principalement, des méthodes utilitaires utilisé partout. Je pourrais le diviser en aides distincts, mais ils seraient au nombre de 30, tous avec une méthode chacun ... cela semble improductif

165
Orion Edwards

Si une méthode sur un module est transformée en une fonction de module, vous pouvez simplement l’appeler hors de Mods comme si elle avait été déclarée comme

module Mods
  def self.foo
     puts "Mods.foo(self)"
  end
end

L'approche module_function ci-dessous évitera de casser les classes qui incluent tous les Mods.

module Mods
  def foo
    puts "Mods.foo"
  end
end

class Includer
  include Mods
end

Includer.new.foo

Mods.module_eval do
  module_function(:foo)
  public :foo
end

Includer.new.foo # this would break without public :foo above

class Thing
  def bar
    Mods.foo
  end
end

Thing.new.bar  

Cependant, je suis curieux de savoir pourquoi un ensemble de fonctions non liées sont toutes contenues dans le même module en premier lieu?

Edited pour montrer que cela fonctionne toujours si public :foo s'appelle après module_function :foo

128
dgtized

Je pense que le moyen le plus rapide de simplement lancer un appel unique (sans modifier les modules existants ni en créer de nouveaux) serait le suivant:

Class.new.extend(UsefulThings).get_file
134
dolzenko

Une autre façon de le faire si vous "possédez" le module est d'utiliser module_function.

module UsefulThings
  def a
    puts "aaay"
  end
  module_function :a

  def b
    puts "beee"
  end
end

def test
  UsefulThings.a
  UsefulThings.b # Fails!  Not a module method
end

test
78
Dustin

Pour appeler une méthode d'instance de module sans inclure le module (et sans créer d'objets intermédiaires):

class UsefulWorker
  def do_work
    UsefulThings.instance_method(:format_text).bind(self).call("abc")
    ...
  end
end
17
renier

Si vous souhaitez appeler ces méthodes sans inclure le module dans une autre classe, vous devez les définir en tant que méthodes de module:

module UsefulThings
  def self.get_file; ...
  def self.delete_file; ...

  def self.format_text(x); ...
end

et alors vous pouvez les appeler avec

UsefulThings.format_text("xxx")

ou

UsefulThings::format_text("xxx")

Mais de toute façon, je vous recommanderais de ne mettre que des méthodes apparentées dans un module ou dans une classe. Si vous ne voulez pas inclure une seule méthode du module, cela ressemble à une mauvaise odeur de code et ce n’est pas bon Ruby) pour assembler des méthodes non liées.

17

Premièrement, je vous recommande de diviser le module en éléments utiles dont vous avez besoin. Mais vous pouvez toujours créer une classe qui étend cela pour votre invocation:

module UsefulThings
  def a
    puts "aaay"
  end
  def b
    puts "beee"
  end
end

def test
  ob = Class.new.send(:include, UsefulThings).new
  ob.a
end

test
6
Dustin

Si je comprends bien la question, vous souhaitez mélanger certaines méthodes d'instance d'un module dans une classe.

Commençons par examiner comment Module # include fonctionne. Supposons que nous ayons un module UsefulThings contenant deux méthodes d'instance:

module UsefulThings
  def add1
    self + 1
  end
  def add3
    self + 3
  end
end

UsefulThings.instance_methods
  #=> [:add1, :add3]

et Fixnumincludes ce module:

class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  include UsefulThings
end

On voit ça:

Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" }
  #=> [:add2, :add3, :add1] 
1.add1
2 
1.add2
cat
1.add3
dog

Vous attendiez-vous UsefulThings#add3 pour remplacer Fixnum#add3, pour que 1.add3 retournerais 4? Considère ceci:

Fixnum.ancestors
  #=> [Fixnum, UsefulThings, Integer, Numeric, Comparable,
  #    Object, Kernel, BasicObject] 

Lorsque la classe includes le module, le module devient la superclasse de la classe. Donc, à cause du fonctionnement de l'héritage, envoyer add3 à une instance de Fixnum causera Fixnum#add3 être appelé, retournant dog.

Ajoutons maintenant une méthode :add2 à UsefulThings:

module UsefulThings
  def add1
    self + 1
  end
  def add2
    self + 2
  end
  def add3
    self + 3
  end
end

Nous souhaitons maintenant Fixnum seulement include les méthodes add1 et add3. Si tel est le cas, nous espérons obtenir les mêmes résultats que ci-dessus.

Supposons, comme ci-dessus, que nous exécutons:

class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  include UsefulThings
end

Quel est le résultat? La méthode indésirable :add2 est ajouté à Fixnum, :add1 est ajouté et, pour les raisons que j'ai expliquées ci-dessus, :add3 n'est pas ajouté. Donc, tout ce que nous avons à faire, c'est undef:add2. Nous pouvons le faire avec une méthode d'assistance simple:

module Helpers
  def self.include_some(mod, klass, *args)
    klass.send(:include, mod)
    (mod.instance_methods - args - klass.instance_methods).each do |m|
      klass.send(:undef_method, m)
    end
  end
end

que nous invoquons comme ceci:

class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  Helpers.include_some(UsefulThings, self, :add1, :add3)
end

Ensuite:

Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" }
  #=> [:add2, :add3, :add1] 
1.add1
2 
1.add2
cat
1.add3
dog

quel est le résultat que nous voulons.

4
Cary Swoveland

A. Au cas où vous voudriez toujours les appeler de manière "qualifiée", de manière autonome (UsefulThings.get_file), alors rendez-les simplement statiques comme d'autres l'ont fait remarquer,

module UsefulThings
  def self.get_file; ...
  def self.delete_file; ...

  def self.format_text(x); ...

  # Or.. make all of the "static"
  class << self
     def write_file; ...
     def commit_file; ...
  end

end

B. Si vous souhaitez toujours conserver l’approche mixin dans les mêmes cas, ainsi que l’invocation autonome unique, vous pouvez avoir un module one-liner qui s’étend lui-même avec le mixin:

module UsefulThingsMixin
  def get_file; ...
  def delete_file; ...

  def format_text(x); ...
end

module UsefulThings
  extend UsefulThingsMixin
end

Donc les deux fonctionnent alors:

  UsefulThings.get_file()       # one off

   class MyUser
      include UsefulThingsMixin  
      def f
         format_text             # all useful things available directly
      end
   end 

IMHO c'est plus propre que module_function pour chaque méthode - au cas où vous voudriez toutes.

4
inger

Après presque 9 ans, voici une solution générique:

module CreateModuleFunctions
  def self.included(base)
    base.instance_methods.each do |method|
      base.module_eval do
        module_function(method)
        public(method)
      end
    end
  end
end

RSpec.describe CreateModuleFunctions do
  context "when included into a Module" do
    it "makes the Module's methods invokable via the Module" do
      module ModuleIncluded
        def instance_method_1;end
        def instance_method_2;end

        include CreateModuleFunctions
      end

      expect { ModuleIncluded.instance_method_1 }.to_not raise_error
    end
  end
end

Le truc malheureux que vous devez appliquer consiste à inclure le module une fois les méthodes définies. Sinon, vous pouvez également l'inclure après que le contexte soit défini comme ModuleIncluded.send(:include, CreateModuleFunctions).

Ou vous pouvez l'utiliser via le reflection_utils gem.

spec.add_dependency "reflection_utils", ">= 0.3.0"

require 'reflection_utils'
include ReflectionUtils::CreateModuleFunctions
0
thisismydesign