DEV Community

Tomer Kimia
Tomer Kimia

Posted on

4 Ways to Write Module Functions

This is not a best practices post.

Let's say that you wanted to add some functions that operated on strings, but didn't want to monkeypatch the String class. Let's also say that you take inspiration from Apache Commons' Lang library and wanted to create your own version of StringUtils.

We want to end up with functions can be called like this:

StringUtils.alphanumeric? "Hello123" # true
StringUtils.alphanumeric? "Hello World" # false

Since StringUtils is not an object that we'd want to instantiate, we'll write it as a module.

Now you have to make choice: how do we want to write the code so that the module itself can be the receiver for these methods.

Option 1: Prepend self to the method name

module StringUtils
  def self.alphanumeric?(str)
    /\A[0-9a-zA-Z]+\z/.match? str
  end
end

This is equivalent to def StringUtils.alphanumeric?, which is an example hosted on the official Ruby docs website. However, Rubocop will complain and suggest that you switch to self.alphanumeric? instead.

Option 2: Put them in the "Singleton Class" (Eigenclass)

module StringUtils
  class << self
    def alphanumeric?(str)
      /\A[0-9a-zA-Z]+\z/.match? str
    end
  end
end

I was thoroughly confused when I first read this type of code. Plus, if you search Ruby Singleton class and you'll find dozens of unrelated hits about implementing the singleton pattern. Good thing we came up with the much clearer name: "eigenclass".

Anyway, this option is nice because you can group all of the methods you want the module to receive inside a block. For that reason, I often see this option used inside of classes, alongside instance methods.

Option 3: extend self

module StringUtils
  extend self

  def alphanumeric?(str)
    /\A[0-9a-zA-Z]+\z/.match? str
  end
end

I saw this in the Pact source code and was taken aback.

This code allows you to call StringUtils.alphanumeric?, but it also allows you to include StringUtils in some other class.

I have a hard time thinking of a use case in which you'd want that.

Option 4: module_function

module StringUtils
  module_function

  def alphanumeric?(str)
    /\A[0-9a-zA-Z]+\z/.match? str
  end
end

All methods defined under the module_function line can be received by the module itself, AND will be made private so they can't be included in classes. You can also put module_function on the bottom of you module and give it symbol arguments so that you can pick and choose the affected methods:

module StringUtils
  def alphanumeric?(str)
    /\A[0-9a-zA-Z]+\z/.match? str
  end

  module_function :alphanumeric?
end

Conclusion

I won't say which option I'd choose to write this code. While there are nuanced arguments for each option, they all get the job done. I started this post by saying that it's not about best practice.

It's often hard to wrap your head around the patterns used in code you haven't written yourself. This is especially notable when someone writes code that you would have written differently. I hope that seeing these snippets side by side will help you recognize what's going on when reading other people's code in the future.

Bonus
Here's a fun fact: If you type extend self while using Rubocop, it'll instruct you to use module_method instead. However, these are clearly not the same! More here.

Top comments (0)