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

前端 未结 10 1325
情歌与酒
情歌与酒 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 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.

提交回复
热议问题