In Ruby we have at least 3 ways to expose a function so it can be called directly on the module:

  • define a method with self prefix
  • define a method and use extend self
  • define a method and use module_function

Let’s compare them and see how they work.

self

module A
  def self.foo
    puts "I'am A.foo"
  end
end

A.foo
#=> I'am A.foo

When you define a method with self prefix it becomes defined on the module eigenclass. If you’ve never heard about eigenclass I recommend you to check this article. In simple words eigenclass is a unique class associated with every Ruby object which allows you to define methods for it.

You can check A’s eigenclass methods and its ancestors like this:

class << A
  p self.public_instance_methods
  p self.ancestors
end
#=> [:foo, :<=>, :include, :<=, :>=, :==, :===, ... ]
#=> [#<Class:A>, Module, Object, Kernel, BasicObject]

As you see, we have a public method foo defined on the A’s module eigenclass.

module_function

module_function allows you to expose instance methods so they can be called as they would be class methods.

module B
  def foo
    puts "I'm B.foo"
  end

  module_function :foo
end

B.foo
#=> I'am B.foo

If you check B’s eigenclass you will see the same result as before:

class << B
  p self.public_instance_methods
  p self.ancestors
end
#=> [:foo, :<=>, :include, :<=, :>=, :==, :===, ... ]
#=> [#<Class:B>, Module, Object, Kernel, BasicObject]

But there is a small difference between those 2 solutions: module_function copies the function so if you try to override the original (instance) one, the change won’t propagated unless you call module_function once again:

module B
  def foo
    puts "I'm overriden B.foo"
  end
end

B.foo
#=> I'am B.foo

module B
  module_function :foo
end

B.foo
#=> I'm overriden B.foo

extend self

extend self basically extends the module with functions defined in itself. Let’s see how it works:

module C
  extend self

  def foo
    puts "I'am C.foo"
  end
end

C.foo
#=> I'am C.foo
#=> nil

In opposite to module_function, overridden methods stay in sync:

module C
  def foo
    puts "I'am new C.foo"
  end
end

C.foo
#=> I'am new C.foo

Let’s check the C’s eigenclass:

class << C
  p self.public_instance_methods
  p self.ancestors
end
#=> [:foo, :<=>, :include, :<=, :>=, :==, :===, ... ]
#=> [#<Class:C>, C, Module, Object, Kernel, BasicObject]

Looks almost the same except that C module exists in the C’c eigenclass ancestors. You might ask if it’s a big deal. In some cases it might be. Let me show you an example:

Imagine that you would like to override original method but still be able to call it from there.

Unfortunately methods defined using self or exposed with module_function were defined on the eigenclass so we are not able to use super when we are overriding methods directly (alternative solution is shown below):

module A
  def self.foo
    puts "I'm a new A.foo"
    super
  end
end

A.foo
#=> I'm a new A.foo
#=> NoMethodError: super: no superclass method `foo' for A:Module
#=> Did you mean?  fork

Let’s check B now:

module B
  def self.foo
    puts "I'm a new B.foo"
    super
  end
end

B.foo
#=> I'm a new B.foo
#=> NoMethodError: super: no superclass method `foo' for B:Module
#=> Did you mean?  fork

And finally extend self:

module C
  def self.foo
    puts "I'm a new C.foo"
    super
  end
end

C.foo
#=> I'm a new C.foo
#=> I'am C.foo

Because C is also an ancestor of C’s eigenclass, we are able to call super from overridden method.

As I mentioned above there is a solution to override A.foo and B.foo methods - it requires using Module#prepend method in order to place a module (it can be a anonymous one) in front of A’s and B’c eigenclasses:

module A
  class << self
    prepend(Module.new do
      def foo
        puts "I'm a new A.foo"
        super
      end
    end)
  end
end

A.foo
#=> I'm a new A.foo
#=> I'am A.foo

class << A
  p self.ancestors
end
#=> [#<Module:0x007fd8e0052628>, #<Class:A>, Module, Object, Kernel, BasicObject]

And now the same for B:

module B
  class << self
    prepend(Module.new do
      def foo
        puts "I'm a new B.foo"
        super
      end
    end)
  end
end

B.foo
#=> I'm a new B.foo
#=> I'm B.foo

class << B
  p self.ancestors
end
#=> [#<Module:0x007feb7c90f4b0>, #<Class:B>, Module, Object, Kernel, BasicObject]

As you see the differences are very substantial and they’re mostly about possibilities for methods overriding (which might be helpful if you are designing some kind of plugin based architecture).

That’s all for now. Thanks for reading.