Home > Software engineering >  Using `extend self` and `class << self` in Ruby Modules
Using `extend self` and `class << self` in Ruby Modules

Time:11-27

I sometimes write modules that will only contain module methods (as opposed to module instance methods) (are there better names for these?). These modules should not be included in classes because that would have no effect and be misleading to a reader. So I'd like it to be as clear as possible to the reader that these modules contain no instance methods.

If I define all methods with .self, then a reader has to inspect all methods to ensure that this module contains no instance methods. If I instead use class << self or extend self then it is automatic; as soon as the reader sees this, they know.

I think extend self is best becuase with class << self one has to find its corresponding end; that is, it may not apply to all methods in the module.

So is it a good idea, and a best practice, to use extend self in cases like this?

Also, is there any difference at runtime between enclosing all methods in class << self as opposed to using extend self?

CodePudding user response:

I sometimes write modules that will only contain module methods (as opposed to module instance methods) (are there better names for these?).

Singleton, meaning a class with a single instance. Here that "single instance" is the Module instance.

If I define all methods with .self, then a reader has to inspect all methods to ensure that this module contains no instance methods

The module's documentation should make this clear. If the user of a module has to study the code to understand your module, that is a documentation failure.

What does extend self do?

So I'd like it to be as clear as possible to the reader that these modules contain no instance methods.

extend self does the opposite. It makes all the instance methods also be class methods. It's equivalent to YourModule.extend(YourModule).

module YourModule
  def some_method
    23
  end

  extend self
end

Is the same as...

module YourModule
  def some_method
    23
  end
end

YourModule.extend(YourModule)

Which is similar to...

module YourModule
  def some_method
    23
  end

  def self.some_method
    23
  end
end

Why would you do this? To allow both...

YourModule.some_method

and also...

class SomeClass
  extend YourModule
end

SomeClass.some_method

There are edge cases where you might want this, but for general use I would argue this is an anti-pattern. The first is using a module as a singleton, the second is using the module as a mixin or trait. These are two rather different design goals for a module. Trying to be both will compromise the design of both.

Pros and Cons.

Since the primary use case of being both a singleton and a mixin is an anti-pattern, I would argue use class << self, with def self.method occasionally, and module_function and extend self never.

class << self

Pros

  • All class definitions are grouped together.
  • The block scope makes it clear what affects the class and what affects the instances.
  • Indentation makes it clear what is in the block.
  • IDEs can clearly identify what is in the block.
  • It allows using normal declarations like attr_accessor on the class.
  • It is documented.
  • It is common.
  • Rubocop approved.

Cons

  • When looking at an individual method, it's not as obvious as def self.method.

def self.method

Pros

  • It's obvious it's a class method from looking at the method.
  • It is documented.
  • It is common.
  • Rubocop approved.

Cons

  • You might forget to add the self..
  • It allows mixing of class and instance methods making the reader hunt through the code.
  • It does not help using attr_accessor and friends on the class.

extend self

Pros

  • It allows your module to act as both a singleton (YourModule.method) and a mixin (extend YourModule)... which is also a con.

Cons

  • It is obscure; many (most?) won't know to look for it or what it means if they find it.
  • It is not documented (or if it is, I can't find it).
  • Individual methods look like instance methods.
  • It can appear anywhere in the module, and there's no consensus where it should go, making it action at a distance.
  • It affects the meaning of code before it, the one case I can think of this in Ruby, further making it action at a distance.
  • Rubocop prefers module_function to extend self, though doesn't explain why. For my guesses, see below.
  • It allows your module to act as both a singleton (YourModule.method) and a mixin (extend YourModule). Those are two rather different use cases making this an anti-pattern.

module_function

I've never heard of this either, but it came up when searching for extend self. I would also say to never use this, use class << self, but it's better than extend self.

Pros

Cons

  • It is obscure; many (most?) won't know to look for it or what it means when they find it.
  • Individual methods look like instance methods.
  • It affects the meaning of distant code after it making it action at a distance.

CodePudding user response:

I don't see why it should matter how you decide to define the module methods. Consider simply raising an exception if the module is included in another module (which may be a class). You can do that with the callback (a.k.a. "hook") method Module#included. Here's an example.

module M
  # This module is not to be included in a class because
  # it contains no instance methods.
  
  def self.included(klass)
    raise "\nYou intended to include this module in #{klass}. You must be out of\nyour mind! It does no harm but there is no point in doing so\nbecause this module contains no instance methods. Duh!"
  end
  
  def self.hi
    puts "Hi, guys"
  end
end
M.hi
Hi, guys
class C
  include M
end
RuntimeError: 
You intended to include this module in C. You must be out of
your mind! It does no harm but there is no point in doing so
because this module contains no instance methods. Duh!
  • Related