Search code examples
rubymodulemetaprogrammingmixinscomposition

Is it possible to mix-in a module method?


Let's say I have a module that declares a module method (not an instance method):

module M
  def self.foo
    puts 'foo'
  end
end

Now let's say I want to mix-in M.foo into another class C such that C.foo is defined.

Finally, I want to do this without changing the way M.foo is defined and without just creating a method in C that calls M.foo. (i.e. rewriting foo as an instance method doesn't count. Neither does using module_function.)

Is this impossible in Ruby?


Solution

  • I want to do this without changing the way M.foo is defined

    Unfortunately, that's not possible. Ruby only allows to include modules, not classes. foo however is defined on M's singleton class which is a class. Therefore, you can't include it. The same restriction applies to extend. Trying to do so results in a TypeError:

    module M
      def self.foo
        puts 'foo'
      end
    end
    
    class C
      extend M.singleton_class # TypeError: wrong argument type Class (expected Module)
    end
    

    You can however achieve what you want by defining foo as an instance method in a separate module which can then be mixed into both, M and C via extend: (that module doesn't have to be nested under M)

    module M
      module SingletonMethods
        def foo
          puts 'foo'
        end
      end
    
      extend SingletonMethods     # <- this makes foo available as M.foo
    end
    
    class C
      extend M::SingletonMethods  # <- this makes foo available as C.foo
    end
    
    

    Or with some metaprogramming magic using Ruby's included callback:

    module M
      module SingletonMethods
        def foo
          puts 'foo'
        end
      end
    
      extend SingletonMethods
    
      def self.included(mod)
        mod.extend(SingletonMethods)
      end
    end
    
    class C
      include M
    end
    

    That's a simplified version of how ActiveSupport::Concern works in Rails.