Create class-level methods when including a module in Ruby.

by Rabbit

The title is important. So important it becomes our singular goal, so let’s say it together. Aloud.

Create class-level methods when including a module.

Notice I didn’t specify whether the module’s methods are instance- or class-level. I will specify that now: in my examples, all module methods will be defined as instance-level methods, but I will show you how to make them class-level methods after being included.

Because words and images are often disconnected from each other when learning, I’m going to illustrate via code exactly what I am talking about:

Rabbit.new.eat # Instance-level method, bad. Not what we want.
Rabbit.eat     # Class-level method, good! This is definitely what we want.

What you are about to see is so simple it’s stupid. While I’m sure many rubyists out there already know what I am about to say, the explanations I’ve read are NOT simple. Observe:

module Consume
 
  def eat
    "You eat a carrot."
  end
 
end
 
class Rabbit
 
  include Consume # creates instance-level methods
  extend Consume  # creates class-level methods
 
end

By including and extending like above, we can do:

>> rabbit = Rabbit.new
=> #<Rabbit:0x36a2810>
>> rabbit.eat # given to us by using include
=> "You eat a carrot."
>> Rabbit.eat # given to us by using extend
=> "You eat a carrot."

Wow. It’s that easy? Yup! But it’s not the whole story. Operating in the context of an object is different from operating in the context of a class, and I doubt the code for one would work well, if at all, in the other. Therefore, it’s necessary to be able to define which methods should be treated as instance- and class-level.

I’ll be honest, achieving this is not as pretty as the code above, but it is easy, and you might learn a thing or two in the process.

First, let’s state a new goal:

Include a module into a class, defining which methods are instance- and class-level.

We’ll start with the class because it’s easy and doesn’t change for the rest of this tutorial:

class Rabbit
 
  include Consume
 
end

Rather than give you the entire module, I’m going to give it to you in pieces. First piece:

module Consume
 
  def eat
    "You eat a carrot."
  end
 
end

Nothing new here. This gives us our typical instance-level method (e.g. Rabbit.new.eat).

Let’s add a method here; don’t worry if it’s new to you, I’ll explain it:

module Consume
 
  def included(receiver)
  end
 
  def eat
    "You eat a carrot."
  end
 
end

What’s included? It’s a built-in callback method that all objects in Ruby can use. Basically, if you define the method, when a module is included into a class, it gets run. Reversely, if you don’t define it, nothing happens. If you’ve used Rails, it’s similar in concept to model validations like before_save and after_create. If you haven’t played with Rails and don’t understand what I mean when I say “callback method,” think of it like a trigger. When you open the door, it pulls the string and a bucket of piss is dropped on your head. Except in Ruby, it’s a bucket of angel kisses, so it’s okay to open the door.

The included method receives the object the module was included into as its argument. In other words, when we say:

class Rabbit
 
  include Consume
 
end

We get:

module Consume
 
  def included(receiver)
    # receiver is the class Rabbit, not an instance of Rabbit.
  end
 
end

So why is this useful? Because of extend! We know that saying include Consume gives us instance-level methods, and extend Consume gives us class-level methods. So now we have a chance to say both. Here’s the next piece of our module:

module Consume
 
  module ClassMethods
  end
 
  def included(receiver)
  end
 
  def eat
    "You eat a carrot."
  end
 
end

Whoa! You didn’t say anything about nested modules! WTF?!

Calm down, it’s more for show than anything else. Think of the second module as really nasty syntactic sugar.

Just like you say def self.my_method to declare an instance method in a class, we say module ClassMethods; ...; end to declare class-level methods for the classes including our modules.

Let’s complete our module:

module Consume
 
  module ClassMethods
    # Still *looks* like an instance-level method. This is what we want.
    def eat
      "You eat a carrot."
    end
  end
 
  def included(receiver)
    receiver.extend ClassMethods
  end
 
  def eat
    "You eat a carrot."
  end
 
end

I know, it looks a little scary, but I promise you it’s not. First off, think of the second module, ClassMethods, as simply that — class-level methods. Imagine that every method defined within ClassMethods looks like def self.method_name.

The classes that use this module will never need to know about the second module, and neither will you, in practice.

We’ve filled out our included method — a whopping one line. To try and explain it, I’m going to use an analogy. Don’t worry, it should be pretty easy:

include Consume
receiver.include Consume

The first and seconds lines are the same. In Ruby, no matter where you are or what you’re doing, you have the implied concept of self. So in line one, self is the class you’re in (Rabbit, in our case), which means we’re calling the include method on self (again, Rabbit, in our case).

The second line makes the receiver of the include method explicit — but again, it’s the same: Rabbit. Of course it could be any class and it will still work.

So our basic anatomy looks like so:

module MyModule
 
  module ClassMethods
    # Instance-level methods I define here will be turned into class-level
    # methods when included into another class.
  end
 
  def included(receiver)
    # There's a dash of meta-magic here, but I'm not afraid, because it's
    # actually really easy to understand if I remember that it's just a different
    # way of saying include MyModule.
  end
 
  def another_method
    # These are standard instance-level methods both in the module AND in
    # any class into which they are included.
  end
 
end

So there you have it. The simple act of including class-level methods into your class via a module is pretty easy, and having an arbitrary set of both isn’t too difficult. But now that you know how, the next (and in my honest opinion, more difficult) question is when.

As always, the answer is, “it depends.” But I have an example. Before writing this post I was working on a module for a Rails app. I needed my module to include class-level methods only — no instance-level methods. So, I naively declared all my methods like def self.my_method, which, as you now know, is incorrect. In fact, Ruby simply ignores those methods, and since all my methods were declared in that manner, my classes inherited no new methods.

It was about that time that I started doing some research and came across a bunch of good stuff on the Ruby Forum about mixing in class-level methods into classes. It spiraled downward into the second solution I showed you (where you use the nested module and the included method), when all I had to do was say extend MyModule. A frustrating experience, but in the end I’m that much more enlightened, and hopefully you are, too!