Can I invoke an instance method on a Ruby module without including it?

前端 未结 10 1299
情歌与酒
情歌与酒 2020-12-07 07:37

Background:

I have a module which declares a number of instance methods

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

  def forma         


        
相关标签:
10条回答
  • 2020-12-07 07:49

    If you want to call these methods without including module in another class then you need to define them as module methods:

    module UsefulThings
      def self.get_file; ...
      def self.delete_file; ...
    
      def self.format_text(x); ...
    end
    

    and then you can call them with

    UsefulThings.format_text("xxx")
    

    or

    UsefulThings::format_text("xxx")
    

    But anyway I would recommend that you put just related methods in one module or in one class. If you have problem that you want to include just one method from module then it sounds like a bad code smell and it is not good Ruby style to put unrelated methods together.

    0 讨论(0)
  • 2020-12-07 07:50

    If a method on a module is turned into a module function you can simply call it off of Mods as if it had been declared as

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

    The module_function approach below will avoid breaking any classes which include all of 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  
    

    However, I'm curious why a set of unrelated functions are all contained within the same module in the first place?

    Edited to show that includes still work if public :foo is called after module_function :foo

    0 讨论(0)
  • 2020-12-07 07:54

    I think the shortest way to do just throw-away single call (without altering existing modules or creating new ones) would be as follows:

    Class.new.extend(UsefulThings).get_file
    
    0 讨论(0)
  • 2020-12-07 07:54

    To invoke a module instance method without including the module (and without creating intermediary objects):

    class UsefulWorker
      def do_work
        UsefulThings.instance_method(:format_text).bind(self).call("abc")
        ...
      end
    end
    
    0 讨论(0)
  • 2020-12-07 07:54

    After almost 9 years here's a generic solution:

    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
    

    The unfortunate trick you need to apply is to include the module after the methods have been defined. Alternatively you may also include it after the context is defined as ModuleIncluded.send(:include, CreateModuleFunctions).

    Or you can use it via the reflection_utils gem.

    spec.add_dependency "reflection_utils", ">= 0.3.0"
    
    require 'reflection_utils'
    include ReflectionUtils::CreateModuleFunctions
    
    0 讨论(0)
  • 2020-12-07 07:56

    Another way to do it if you "own" the module is to use 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
    
    0 讨论(0)
提交回复
热议问题