Class Methods In Ruby: a Thorough Review & Why I Define Them Using class << self

Eliav Lavi
Nov 24, 2017 · 6 min read

Class methods are the source for continuous discussions and disagreements among my colleagues. While some consider them precise and helpful, others feel they are actually pesky and that they tend to make code harder to read and manage. As for me, I find that the truth tends to lean to the latter; I embrace Ruby’s OO nature and I like to think (and read!) in objects. Having said that, sometimes class methods are indeed necessary. From factory methods to complicated metaprogrammed interfaces through ActiveRecord’s models custom query methods, class methods cannot be negated completely, yet should be used sparingly (see this excellent post by Code Climate for more on that).

This post does not concern itself with the “class methods — good or bad?” question; rather, it is a discussion between two stylistic approaches regarding how to notate those class methods when they are needed.

Image for post
Image for post

Style and Style Guides

Having a shared style and following an actual style guide within an organization is important. In a well-articulated write-up Sandi Metz claims that

[…] many stylistic choices are arbitrary, and purely a matter of personal preference. Choosing a style guide means building agreements in areas where we have strong differences of opinion about issues of little significance. It’s not style that matters, but sameness of style. (source)

That is a highly valid claim. Yet, it certainly is important to make the proper choices when picking up style. Similarly to fashion, code style reflects our credo as developers, our values and philosophy. In order to make an informed decision, it’s mandatory to understand the issue at stake well. We all have defined class methods many times, but do we really know how do they work?

Image for post
Image for post

The Singleton Class

an_array = [1, 5, 10]

If we try to run an_array.average we will get NoMethodError since neither Array nor its superclasses have an average method defined in them:

# NoMethodError: undefined method `average' for [1, 5, 10]:Array

We could monkey-patch Array and define an average method in it, but if we needed this method only for our an_array, we could also do this:

def an_array.average
reduce(:+) / count.to_f

Now this would work:

# => 5.333333333333333

Yet executing the same method on another instance of Array would end up in NoMethodError again:

another_array = [1, 3, 7]
# => NoMethodError: undefined method `average' for [1, 3, 7]:Array

That is because behind the scenes Ruby stored the average method in a special class that only an_array is pointing to — its own singleton class:

# => #<Class:#<Array:0x007fcf27848750>>
# => [:average]

Every instance of every Ruby class has its own singleton class which is where its singleton methods are stored, such as the one we have just defined. (Well, almost every object; this is not true for Numeric objects.)
When we call a method upon an object, its singleton class is the first place Ruby will look for that method, before the regular class and its ancestor chain.

Class Methods Are Singleton Methods

We can see that theory in action easily:

Example.is_a? Object
# => true
# => Class
=> #<Class:Example>
=> [:an_instance_method]
=> [:a_class_method]

Calling instance_methods with the false argument simply excludes inherited methods from the methods lists (source).

Choices of Notation

I wish to define methods within the class they belong to. Using
class << self demonstrates that approach clearly — we are defining methods within the actual singleton class scope. When we use
def self.method, though, we are defining a method across scopes: we are present in the regular class scope, but we use Ruby’s ability to define methods upon specific instances from anywhere; self within a class definition is the Class instance we are working on (i.e. the class itself). Therefore, using
def self.method is a leap to another scope, and this feels wrong to me.

Another reason to question the def self.method notation is the ability to define private and protected methods. Ruby does supply the private_class_method method in order to declare a class method as private; there is no equivalent for protected methods though. Also, for private class methods, you have to declare each method as such separately (i.e. you can’t use that simple private in the middle of your class, since that would apply to that class’ instance methods). To sum up, class << self is actually clearer.

Possible Objections

  • “It’s harder to find those class methods in larger classes that way” — to which I admit, yes, it is. If you have that god-class which is 450 lines long, perhaps you should refactor it first and break it down into smaller classes but stick to def self.method meanwhile. If you have to scroll endlessly, it’s easy to miss out where does the scope change. But do refactor that class, yeah?
  • “It is less clear that way” — that is just arbitrary, even uninformed. There is nothing clearer about def self.method. As demonstrated earlier, once you grasp the true meaning of it, def self.method is actually more vague as it mixes scopes. Understanding the theory behind the actions helps in explaining that.
  • “Who cares? Let’s just go with the style-guide” — to which my response is that caring about the details is in the heart of much of our doings. Yes, this is not a major issue; def self.method is not even a code smell. Actually, that whole debate is on the verge of being incidental. Yet the learning process and the gained knowledge involved in understanding each choice is alone worth the discussion. Furthermore, I believe that the class << self notation echoes a better, more stable understanding of Ruby and Object Orientation in Ruby. Lastly, remember that style-guides may change or be altered (carefully, though!).
Image for post
Image for post

I hope you learned something new reading this post. If you have any remarks or questions about this topic, please use the comments! And if you found it interesting or useful, please support it by clapping it👏 .

Ruby Inside

Ruby articles and posts

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store