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

前端 未结 10 1300
情歌与酒
情歌与酒 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:58

    Firstly, I'd recommend breaking the module up into the useful things you need. But you can always create a class extending that for your 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
    
    0 讨论(0)
  • 2020-12-07 08:00

    A. In case you, always want to call them in a "qualified", standalone way (UsefulThings.get_file), then just make them static as others pointed out,

    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. If you still want to keep the mixin approach in same cases, as well the one-off standalone invocation, you can have a one-liner module that extends itself with the mixin:

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

    So both works then:

      UsefulThings.get_file()       # one off
    
       class MyUser
          include UsefulThingsMixin  
          def f
             format_text             # all useful things available directly
          end
       end 
    

    IMHO it's cleaner than module_function for every single method - in case want all of them.

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

    As I understand the question, you want to mix some of a module's instance methods into a class.

    Let's begin by considering how Module#include works. Suppose we have a module UsefulThings that contains two instance methods:

    module UsefulThings
      def add1
        self + 1
      end
      def add3
        self + 3
      end
    end
    
    UsefulThings.instance_methods
      #=> [:add1, :add3]
    

    and Fixnum includes that module:

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

    We see that:

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

    Were you expecting UsefulThings#add3 to override Fixnum#add3, so that 1.add3 would return 4? Consider this:

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

    When the class includes the module, the module becomes the class' superclass. So, because of how inheritance works, sending add3 to an instance of Fixnum will cause Fixnum#add3 to be invoked, returning dog.

    Now let's add a method :add2 to UsefulThings:

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

    We now wish Fixnum to include only the methods add1 and add3. Is so doing, we expect to get the same results as above.

    Suppose, as above, we execute:

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

    What is the result? The unwanted method :add2 is added to Fixnum, :add1 is added and, for reasons I explained above, :add3 is not added. So all we have to do is undef :add2. We can do that with a simple helper method:

    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
    

    which we invoke like this:

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

    Then:

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

    which is the result we want.

    0 讨论(0)
  • 2020-12-07 08:14

    Not sure if someone still needs it after 10 years but I solved it using eigenclass.

    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"
    
    0 讨论(0)
提交回复
热议问题