The Ruby programming language allows for you to extend and modify any class or object’s implementation.
Every Object Is a Class and Every Class Is an Object
It’s important to understand the almost Zen like implementation of Ruby: “Every class is an object and every object is a class.”
Why is that important? Because what you can do to an object you can do to a class and vice versa.
In the following example, I’m using IRB syntax.
The line that starts with >
is the prompt. The line that starts with =>
is the output of the prompted line’s evaluation.
Let’s define the Book
class; nothing special.
> class Book; end
=> nil
Let’s instantiate an instance of Book
by calling Book.new
method. We’ll call that instance dune
.
> dune = Book.new
=> #<Book:0x0000000106b56ce0>
And let’s look at the the dune
instance’s “class” methods:
> dune.methods.grep(/class/)
=> [:singleton_class, :class]
The class
method for dune
returns the Book
:
> dune.class == Book
=> true
What about the singleton_class
? It’s not Book
, it’s that instance’s eigenclass.
> dune.singleton_class
=> #<Class:#<Book:0x00000001006567a0>>
> dune.singleton_class == dune.class
=> false
Why is the singleton_class
and class
important? It is a conceptual foundation of the include
and extend
method calls.
For completeness, let’s look at Book
\’s class method.
> Book.methods.grep(/class/)
=>
[:superclass,
:subclasses,
:class_variable_set,
:class_variables,
:remove_class_variable,
:class_variable_get,
:class_variable_defined?,
:singleton_class?,
:class_exec,
:class_eval,
:public_class_method,
:private_class_method,
:singleton_class,
:class]
There are more Book
class-like methods than class-like methods for an instance of Book
. Let’s look at both the singleton_class
and class
methods for Book
.
> Book.class
=> Class
> Book.singleton_class
=> #<Class:Book>
At this point pause a moment. Get a drink of water. I don’t know if that was a lot (or not) but the conceptual difference between the Book
class and an instance of a Book
is important.
Extend versus Include
Let’s look at our Book example.
First We Explore Include
module Published
# All book shall be published!
def published?
true
end
end
First lets look at the include
behavior; this adds methods to instances of a class.
class Book
include Published
end
All instances of the Book
class will now have the published?
method.
> Book.new.published?
=> true
But the Book
class will not have the published?
method.
> Book.published?
=> undefined method `published?` for Book:Class (NoMethodError)
In fact, you can use the introspection methods methods
and instance_methods
to check what methods are defined on the class and instances of the class.
> Book.methods.include?(:published?)
=> false
> Book.instance_methods.include?(:published?)
=> true
Then We Explore Extend
The extend
behavior adds method’s to the class.
Let’s create a new module.
module Exciting
def exciting?
true
end
end
And let’s have the Book
class extend the Exciting
module.
> Book.extend Exciting
=> Book
end
The Book
class now has the class
method exciting?
.
> Book.exciting?
=> true
But an instance of Book
does not.
> Book.new.exciting?
=> undefined method `exciting?' for #<Book:0x0000000104441470> (NoMethodError)
A quick mnemonic to remember the above: include is for instances (and extend is for classes).
Let’s Extend an Object’s Singleton Class
With the above Book
and Exciting
module, let’s loop back to the every object is a class.
> left_hand_of_darknes = Book.new
=> #<Book:0x000000010523ccc8>
> left_hand_of_darkness.extend Exciting
=> #<Book:0x000000010523ccc8>
> left_hand_of_darkness.exciting?
=> true
> Book.new.exciting?
=> undefined method `exciting?' for #<Book:0x00000001008dc510> (NoMethodError)
In the above, we extend the left_hand_of_darkness
instance’s singleton_class
with the Exciting
module. And for this instantiation, that object has an exciting?
method. But other instances of Book
still do not have the exciting?
method.
Now, should you “extend” the instance? Likely not, but I have in my years of Ruby found two occasions where it was the right approach. I don’t recall them now, but remember that it elegantly and easily solved a problem.
Conclusion
This tour of the difference between a class and an instance helps lay the conceptual foundations of Ruby’s object model. My intention in this perhaps esoteric detour is to highlight the malleability of Ruby.
In upcoming articles, I’ll build on these concepts.