Self vs extend self vs module_function

In Ruby there are 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

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’s eigenclass - a unique class associated with every Ruby object where you to define a new methods.

Check A’s eigenclass methods and its ancestors:

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

A public method foo is defined on the A’s eigenclass.

module_function

module_function allows to expose instance’s 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

B’s eigenclass is the same as in previous example:

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

There is a small difference though. module_function actually copies the method, so overriding the original one, won’t propagate unless module_function is called 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 it:

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

The C’s eigenclass looks almost the same as previous ones:

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

Except that C module also exists in the C’s eigenclass ancestors, which in some cases might be useful.

Consider an example where overridden method should allow to call parent one. Because methods exposed through self or module_function are actually defined on the eigenclass, super is not available:

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
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

It works with extend self though:

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

Since C is also an ancestor of C’s eigenclass,super is accessible in overridden method.

Alternative solution

It’s actually possible to override A.foo and B.foo methods but it requires using Module#prepend to place a module in front of A’s and B’s eigenclass:

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]
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]