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
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:
Usefulthings
et n’apporter que certaines de ses méthodes UsefulThings
, puis déléguez format_text
à cette instance de proxy 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
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
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
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
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
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.
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
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 Fixnum
include
s 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 include
s 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.
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.
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