Differentiating Ruby’s Classes and Objects

Delving into the Foundational Distinctions

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.