extend self in ruby

Neeraj Singh

By Neeraj Singh

on June 28, 2012

Following code was tested with ruby 1.9.3 .

Class is meant for both data and behavior

Lets look at this ruby code.

1class Util
2  def self.double(i)
3   i*2
4  end
5end
6
7Util.double(4) #=> 8

Here we have a Util class. But notice that all the methods on this class are class methods. This class does not have any instance variables. Usually a class is used to carry both data and behavior and ,in this case, the Util class has only behavior and no data.

Similar utility tools in ruby

Now to get some perspective on this discussion lets look at some ruby methods that do similar thing. Here are a few.

1require 'base64'
2Base64.encode64('hello world') #=> "aGVsbG8gd29ybGQ=\n"
3
4require 'benchmark'
5Benchmark.measure { 10*2000 }
6
7require 'fileutils'
8FileUtils.chmod 0644, 'test.rb'
9
10Math.sqrt(4) #=> 2

In all the above cases the class method is invoked without creating an instance first. So this is similar to the way I used Util.double .

However lets see what is the class of all these objects.

1Base64.class #=> Module
2Benchmark.class #=> Module
3FileUtils.class #=> Module
4Math.class #=> Module

So these are not classes but modules. That begs the question why the smart guys at ruby-core implemented them as modules instead of creating a class the way I did for Util.

Reason is that Class is too heavy for creating only methods like double. As we discussed earlier a class is supposed to have both data and behavior. If the only thing you care about is behavior then ruby suggests to implement it as a module.

extend self is the answer

Before I go on to discuss extend self here is how my Util class will look after moving from Class to Module.

1module Util
2  extend self
3
4  def double(i)
5    i * 2
6  end
7end
8
9puts Util.double(4) #=> 8

So how does extend self work

First lets see what extend does.

1module M
2 def double(i)
3  i * 2
4 end
5end
6
7class Calculator
8  extend M
9end
10puts Calculator.double(4)

In the above case Calculator is extending module M and hence all the instance methods of module M are directly available to Calculator.

In this case Calculator is a class that extended the module M. However Calculator does not have to be a class to extend a module.

Now lets try a variation where Calculator is a module.

1module M
2 def double(i)
3  i * 2
4 end
5end
6
7module Calculator
8  extend M
9end
10puts Calculator.double(4) #=> 8

Here Calculator is a module that is extending another module.

Now that we understand that a module can extend another module look at the above code and question why module M is even needed. Why can't we move the method double to module Calculator directly. Let's try that.

1module Calculator
2  extend Calculator
3
4   def double(i)
5    i * 2
6   end
7end
8puts Calculator.double(4) #=> 8

I got rid of module M and moved the method double inside module Calculator. Since module M is gone I changed from extend M to extend Calculator.

One last fix.

Inside the module Calculator what is self. self is the module Calculator itself. So there is no need to repeat Calculator twice. Here is the final version

1module Calculator
2  extend self
3
4   def double(i)
5    i * 2
6   end
7end
8puts Calculator.double(4) #=> 8

Converting A Class into a Module

Every time I would encounter code like extend self my brain will pause for a moment. Then I would google for it. Will read about it. Three months later I will repeat the whole process.

The best way to learn it is to use it. So I started looking for a case to use extend self. It is not a good practice to go hunting for code to apply an idea you have in your mind but here I was trying to learn.

Here is a before snapshot of methods from Util class I used in a project.

1class Util
2  def self.config2hash(file); end
3  def self.in_cents(amount); end
4  def self.localhost2public_url(url, protocol); end
5end

After using extend self code became

1module Util
2  extend self
3
4  def config2hash(file); end
5  def in_cents(amount); end
6  def localhost2public_url(url, protocol); end
7end

Much better. It makes the intent clear and ,I believe, it is in line with the way ruby would expect us to use.

Another usage inline with how Rails uses extend self

Here I am building an ecommerce application and each new order needs to get a new order number from a third party sales application. The code might look like this. I have omitted the implementation of the methods because they are not relevant to this discussion.

1class Order
2  def amount; end
3  def buyer; end
4  def shipped_at; end
5  def number
6    @number || self.class.next_order_number
7  end
8
9  def self.next_order_number; 'A100'; end
10end
11
12puts Order.new.number #=> A100

Here the method next_order_number might be making a complicated call to another sales system. Ideally the class Order should not expose method next_order_number . So we can make this method private but that does not solve the root problem. The problem is that model Order should not know how the new order number is generated. Well we can move the method next_order_number to another Util class but that would create too much distance.

Here is a solution using extend self.

1module Checkout
2  extend self
3
4  def next_order_number; 'A100'; end
5
6  class Order
7    def amount; end
8    def buyer; end
9    def shipped_at; end
10    def number
11      @number || Checkout.next_order_number
12    end
13  end
14end
15
16puts Checkout::Order.new.number #=> A100

Much better. The class Order is not exposing method next_order_number and this method is right there in the same file. No need to open the Util class.

To see practical examples of extend self please look at Rails source code and search for extend self. You will find some interesting usage.

This is my first serious attempt to learn usage of extend self so that next time when I come across such code my brain does not freeze. If you think I have missed out something then do let me know.

Stay up to date with our blogs. Sign up for our newsletter.

We write about Ruby on Rails, ReactJS, React Native, remote work,open source, engineering & design.