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 à partir d'une classe. Voici comment vous faites normalement cela en rubis:
class UsefulWorker
include UsefulThings
def do_work
format_text("abc")
...
end
end
Problème
include UsefulThings
apporte toutes les méthodes de UsefulThings
. Dans ce cas, je veux seulement format_text
et explicitement ne veux pas get_file
et delete_file
.
Je peux voir plusieurs solutions possibles à cela:
- Invoquez d'une manière ou d'une autre 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)
- Incluez
Usefulthings
et n'apportez que certaines de ses méthodes- Je ne sais pas non plus comment / si cela peut être fait
- Créez une classe de proxy, incluez
UsefulThings
-la, puis déléguezformat_text
à cette instance de proxy- Cela fonctionnerait, mais les classes proxy anonymes sont un hack. Beurk.
- Divisez le module en 2 modules plus petits ou plus
- Cela fonctionnerait également, et c'est probablement la meilleure solution à laquelle je puisse penser, mais je préférerais l'éviter car je me retrouverais 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? Cela ApplicationHelper
provient d'une application de rails, que notre équipe a de facto choisie comme dépotoir pour tout ce qui n'est pas assez spécifique pour appartenir à ailleurs. Principalement des méthodes utilitaires autonomes qui sont utilisées partout. Je pourrais le diviser en assistants séparés, mais il y en aurait 30, tous avec 1 méthode chacun ... cela semble improductif
Module#included
rappel pour déclencher uninclude
de l'autre. Laformat_text
méthode pourrait être déplacée dans son propre module, car elle semble être utile seule. Cela rendrait la gestion un peu moins lourde.module UT; def add1; self+1; end; def add2; self+2; end; end
et que vous vouliez utiliseradd1
mais pasadd2
en classeFixnum
. Comment cela aiderait-il d'avoir des fonctions de module pour cela? Est-ce que je manque quelque chose?Réponses:
Si une méthode sur un module est transformée en une fonction de module, vous pouvez simplement l'appeler hors des 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?
Modifié pour montrer qui inclut toujours fonctionne si
public :foo
est appelé aprèsmodule_function :foo
la source
module_function
passant , transforme la méthode en une méthode privée, ce qui briserait un autre code - sinon ce serait la réponse acceptéeFiles.truncate
et unStrings.truncate
et que je veux utiliser les deux dans la même classe, explicitement. Créer une nouvelle classe / instance chaque fois que j'ai besoin d'une méthode spécifique ou modifier l'original n'est pas une bonne approche, même si je ne suis pas un développeur Ruby.Je pense que le moyen le plus court de ne faire qu'un seul appel (sans modifier les modules existants ou en créer de nouveaux) serait le suivant:
la source
Object.new.extend(UsefulThings).get_file
Une autre façon de le faire si vous "possédez" le module est de l'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
la source
ModuleName.method :method_name
pour obtenir un objet de méthode et l'appeler viamethod_obj.call
. Sinon, je devrais lier la méthode à une instance de l'objet d'origine, ce qui n'est pas possible si l'objet d'origine est un module. En réponse à Orion Edwards,module_function
rend la méthode d'instance d'origine privée. ruby-doc.org/core/classes/Module.html#M001642def self.a; puts 'aaay'; 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 ensuite 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 liées dans un module ou dans une classe. Si vous rencontrez un problème et souhaitez inclure une seule méthode du module, cela ressemble à une mauvaise odeur de code et ce n'est pas un bon style Ruby de rassembler des méthodes non liées.
la source
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
la source
format_text
suppose peut-être l'existence d'une autre méthode fournie par le module, qui (généralement) ne sera pas présente.Tout d'abord, je vous recommande de diviser le module en éléments utiles dont vous avez besoin. Mais vous pouvez toujours créer une classe étendant cela pour votre appel:
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
la source
A. Dans le cas où vous voudriez toujours les appeler de manière "qualifiée" et autonome (UsefulThings.get_file), alors rendez-les statiques comme d'autres l'ont souligné
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
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 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 tous.la source
extend self
est un langage courant.Si je comprends bien la question, vous souhaitez mélanger certaines des méthodes d'instance d'un module dans une classe.
Commençons par examiner le fonctionnement du module # include . Supposons que nous ayons un module
UsefulThings
qui contient 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
à passer outreFixnum#add3
, pour que1.add3
cela revienne4
? Considère ceci:Fixnum.ancestors #=> [Fixnum, UsefulThings, Integer, Numeric, Comparable, # Object, Kernel, BasicObject]
Lorsque la classe
include
est le module, le module devient la superclasse de la classe. Ainsi, en raison du fonctionnement de l'héritage, l'envoiadd3
à une instance deFixnum
provoqueraFixnum#add3
l'appel, le retourdog
.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
àinclude
seulement les méthodesadd1
etadd3
. Si tel est le cas, nous espérons obtenir les mêmes résultats que ci-dessus.Supposons, comme ci-dessus, que nous exécutions:
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éeFixnum
,:add1
est ajoutée et, pour les raisons que j'ai expliquées ci-dessus,:add3
n'est pas ajoutée. Donc tout ce que nous avons à faire, c'estundef
:add2
. Nous pouvons le faire avec une méthode d'aide 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.
la source
Je ne sais pas si quelqu'un en a encore besoin après 10 ans, mais je l'ai résolu en utilisant la classe propre.
module UsefulThings def useful_thing_1 "thing_1" end class << self include UsefulThings end end class A include UsefulThings end class B extend UsefulThings end UsefulThings.useful_thing_1 # => "thing_1" A.new.useful_thing_1 # => "thing_1" B.useful_thing_1 # => "thing_1"
la source
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 est d'inclure le module une fois que les méthodes ont été définies. Vous pouvez également l'inclure une fois que le contexte est défini comme
ModuleIncluded.send(:include, CreateModuleFunctions)
.Ou vous pouvez l' utiliser via le reflection_utils bijou.
spec.add_dependency "reflection_utils", ">= 0.3.0" require 'reflection_utils' include ReflectionUtils::CreateModuleFunctions
la source