Did you know you a class can inherit from Module?

class Foo < Module
end

Foo     # => Foo (a Foo class)
Foo.new # => <Foo:0x0000563adb5c4ab0> (an instance of Foo class)

This works because Module is a class, Module.class # => Class, so it can be used in classical inheritence.

Foo is a class and Foo.new returns an instance of that class which includes Module in its ancestors.

Anything which inherits from Module can be included in another class.

class Bar
  include Foo.new
end

We have to call #new to get an object which as Module in its ancestors.

Yeah, but why would you want to do this?

One practical use is when you want a module to be stateful, that is be configurable.

Firstly let’s look at a regular, and very much contrived, stateless module.

module Foo
  def hello
    'hi'
  end
end

class Bar
  include Foo
end

Bar.new.hello # => 'hi'

Now let’s say we want the method, #hello, to be configurable. We want the user of the module to configure the name of the method the module is going to define. We might reach for define_method to create our method dynamically but but how do we pass the desired method name in to the module as configuration?

Because we are defining a class we have an initialize method to which we can pass arguments.

class Foo < Module
  def initialize(method_name)
    define_method method_name do
      'hi'
    end

    super()
  end
end

class Bar
  include Foo.new(:bonjour)
end

Bar.new.bonjour # => 'hi'

Because the block passed to define_method is a closure, the scope in which it is created is remembered and accessible inside the block when it is called. Therefore we have access to the any arguments passed to #initialize.

Our module has state, which we could expose if desired.

class Foo < Module
  attr_reader :method_name

  def initialize(method_name)
    @method_name = method_name
  end
end

foo = Foo.new('bonjour)
foo.method_name # => 'bonjour'

When creating a module by inheriting from Module a few other things need to be adjusted compared to using module to define a module.

Methods defined in our class are only avalible to instances of the class and are not available to the class in which the module is included as would be the case with a module defined using the module keyword.

module Foo
  def welcome
    'welcome'
  end
end

class Bar
  include Foo
end

Bar.new.welcome # => welcome

But with a class inhertied module:

class Foo < Module
  def welcome
    'welcome'
  end
end

class Bar
  include Foo.new
end

Bar.new.welcome # => MethodMissingError

Instead we need to do the following:

class Foo < Module
  def included(descendant)
    super
    descendant.send(:include, Methods)
  end

  module Methods
    def welcome
      'welcome'
    end
  end
end

class Bar
  include Foo.new
end

Bar.new.welcome # => 'welcome'

So can we get rid the need for .new when including the module?

Kind of, here are a few options.

Uppercase method on parent module

module MyLibrary 
  def self.Foo(*args)
    Foo.new(*args)
  end

  class Foo < Module
    def initialize(options = {})
      super()
      puts options
    end
  end
end

class Pub
  include MyLibrary::Foo(a: :b)
  include MyLibrary::Foo()  
  include MyLibrary::Foo # <- does not work as Foo class, of type Class, is returned, the MyLibrary#Foo method is not called
end

Bar.new

This works for two reasons, class methods can be called via . or :: and methods can be capitalized making them look like references to constants. However the final example does not work because the constant Foo has presedence over the method Foo.

Square brackets

class Foo < Module
  def self.[](options = {})
    new(options)
  end

  def initialize(options = {})
    super()
    puts options
  end
end

class Bar
  include Foo[a: :b]
  include Foo[]  
  include Foo # does not work, Foo is a class of type Class, the `[]` method is not called
end

Bar.new

Lowercase method on parent module

This is my prefered method, no pun intended.

module MyLibrary
  def self.foo(options = {})
    Foo.new(options)
  end

  class Foo < Module    
    def initialize(options = {})
      super()
      puts options
    end
  end
end

class Bar
  include MyLibrary.foo
  include MyLibrary.foo(a: :b)
end

Bar.new

Finally, here is a template to use

module MyLibrary
  def self.foo(*args)
    Foo.new(*args)
  end

  class Foo < Module
    attr_reader :strict

    def initialize(options = {})
      options = parse_options(options)

      @strict = options.fetch(:strict, true)

      define_method :on_event do |name, payload|
        if respond_to?(name)
          public_send(name, payload)
        else
          raise(NoMethodError) if strict
        end
      end
    end

    def included(descendant)
      super
      descendant.send(:include, Methods)
    end

    module Methods
      # this will be a method on a target class
      def welcome
        'welcome'
      end
    end

    private

    # this is in scope to initialize
    def parse_options(options)
      # ...
    end
  end
end

class Bar
  include MyLibrary.foo
end