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)