June 28, 2012
Following code was tested with ruby 1.9.3 .
Lets look at this ruby code.
class Util
def self.double(i)
i*2
end
end
Util.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.
Now to get some perspective on this discussion lets look at some ruby methods that do similar thing. Here are a few.
require 'base64'
Base64.encode64('hello world') #=> "aGVsbG8gd29ybGQ=\n"
require 'benchmark'
Benchmark.measure { 10*2000 }
require 'fileutils'
FileUtils.chmod 0644, 'test.rb'
Math.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.
Base64.class #=> Module
Benchmark.class #=> Module
FileUtils.class #=> Module
Math.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.
Before I go on to discuss extend self
here is how my Util
class will look
after moving from Class
to Module
.
module Util
extend self
def double(i)
i * 2
end
end
puts Util.double(4) #=> 8
First lets see what extend does.
module M
def double(i)
i * 2
end
end
class Calculator
extend M
end
puts 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.
module M
def double(i)
i * 2
end
end
module Calculator
extend M
end
puts 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.
module Calculator
extend Calculator
def double(i)
i * 2
end
end
puts 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
module Calculator
extend self
def double(i)
i * 2
end
end
puts Calculator.double(4) #=> 8
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.
class Util
def self.config2hash(file); end
def self.in_cents(amount); end
def self.localhost2public_url(url, protocol); end
end
After using extend self
code became
module Util
extend self
def config2hash(file); end
def in_cents(amount); end
def localhost2public_url(url, protocol); end
end
Much better. It makes the intent clear and ,I believe, it is in line with the way ruby would expect us to use.
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.
class Order
def amount; end
def buyer; end
def shipped_at; end
def number
@number || self.class.next_order_number
end
def self.next_order_number; 'A100'; end
end
puts 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
.
module Checkout
extend self
def next_order_number; 'A100'; end
class Order
def amount; end
def buyer; end
def shipped_at; end
def number
@number || Checkout.next_order_number
end
end
end
puts 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.
If this blog was helpful, check out our full blog archive.