The Pragmatic Studio

Write Macros in Ruby

April 14, 2015

In Rails you use class-level declarations such as has_many, belongs_to, and other so-called “macros” all the time. For example:

class Movie < ActiveRecord::Base
  has_many :reviews
end

class Project < ActiveRecord::Base
  has_many :tasks
end

Look familiar?

Folks new to Rails (and Ruby) often assume these declarations are a magical aspect of Rails. In fact, there is no magic—it’s just Ruby code. Indeed, Ruby makes programming in this declarative style easier than you might think.

In this short video tutorial we recreate a simplified version of the has_many declaration from scratch so you understand how to apply this same powerful technique in your own Ruby code!

Today we’re going to tap into the power of Ruby objects and methods to write a class-level declaration, sometimes called a “macro”. Here’s an example of what I’m talking about from Rails…

class Movie < ActiveRecord::Base
  has_many :reviews
end

class Project < ActiveRecord::Base
  has_many :tasks
end

The first time you encounter a declaration like has_many, it looks like something built in to the Ruby language or some magical aspect of Rails. But in fact, it’s simply Ruby code. Ruby itself makes programming in this declarative style easier than you might think. And once you understand how it works, you’ll be more confident with Rails and be able to use this same powerful technique in your own Ruby code.

So let’s create a simplified version of this code from scratch, building up from the underlying principles that make it work.

Singleton Methods On An Object

Here’s a simple String object, and we call the upcase method which is defined in the String class:

dog1 = "Rosco"

puts dog1.upcase  # => ROSCO

And no surprise, it prints out the dog’s name upcased: “ROSCO”.

So we can call any method in the String class, but Ruby also lets us define methods on a specific object. For example, here we define a hunt method on the dog1 object:

def dog1.hunt
  puts "WOOF!"
end

dog1.hunt  # => WOOF!

And when we call the hunt method on the dog1 object, it prints “WOOF!”.

OK, now here’s a different String object:

dog2 = "Snoopy"

Now what happens if we try to call the hunt object on the dog2 object?

dog2.hunt  # => undefined method `hunt`

Well, this dog don’t hunt! The hunt method is only defined on the dog1 object. You’ll often hear this referred to as a singleton method because the hunt method is only defined on a single object: the dog1 object in this case.

That’s interesting, but when would you ever want to do this? Turns out, you use singleton methods all the time in Ruby! And we need one to define our has_many declaration.

Classes Are Objects, Too!

Here’s a simple Movie class:

class Movie
end

In Ruby, classes are objects, too. In fact, we can print out the class of the Movie class:

p Movie.class  # => Class

The class of the Movie class is Class. And that in itself is an object, which we can see by printing out the object’s id:

p Movie.class.object_id  # => 70233956488680

Interestingly to note, Movie is a constant that references the Class object.

So classes in Ruby are objects, too. And any given Ruby class is an object of class Class.

Singleton Methods On A Class

So if a Ruby class is an object in its own right, we can treat it like any other object. For example, we can define a singleton method on the Class object referenced by the Movie constant:

movie_class = Movie

def movie_class.my_class_method
  puts "Running class method..."
end

my_class_method is just a singleton method defined on the Movie class object. To call that method, the receiver of the call is the Movie class object.

movie_class.my_class_method   # => "Running class method..."

So that’s basically the same thing we did with the dog1 object earlier. In this case we defined the singleton method on the Class object.

To tidy up this code, we don’t need the temporary movie_class variable since it’s just a reference to the Movie class object. Here’s a version of the code that doesn’t use the temporary variable:

def Movie.my_class_method
  puts "Running class method..."
end

Movie.my_class_method   # => "Running class method..."

And that works just as well!

To make this look more traditional, we can move the method inside the class declaration:

class Movie
  def Movie.my_class_method
    puts "Running class method..."
  end
end

Movie.my_class_method

And that also works!

Here’s the take-away: In Ruby, there is no such thing as a “class method”. my_class_method is just a singleton method defined on the Movie class object.

So that’s the first principle, but it doesn’t look like the has_many declaration quite yet.

Classes Are Executable Code

The second principle is that class definitions are executable code. We can prove that by adding some print statements:

puts "Before class definition"

class Movie
  puts "Inside class definition"

  def Movie.my_class_method
    puts "Running class method..."
  end
end

puts "After class definition"

Movie.my_class_method

And when we run it, we get:

Before class definition
Inside class definition
After class definition
Running class method...

So code is executed during the process of defining the class. And if that’s the case, then we can actually run the my_class_method method inside the Movie class definition:

puts "Before class definition"

class Movie
  puts "Inside class definition"

  def Movie.my_class_method
    puts "Running class method..."
  end

  Movie.my_class_method  # run the method as the class is being defined
end

puts "After class definition"

If we run it now, we get:

Before class definition
Inside class definition
Running class method...
After class definition

So the my_class_method method is called as the class is being defined!

But using the Movie constant seems repetitive. Turns out, during the class definition, Ruby sets the self variable to the class object being defined. Let’s print out the value of self as the class is being defined:

puts "Inside class definition of #{self}"

If we run that, we get:

Before class definition
Inside class definition of Movie
Running class method...
After class definition

So this proves that self is set to the current class object being defined, which is the Movie class object in this case.

That being the case, we can replace Movie with self:

def self.my_class_method
  puts "Running class method..."
end

self.my_class_method

And that works just the same!

Now this self.my_class_method line is starting to look like the has_many class declaration that we want, except for the self. part. In this context, self is the receiver of the my_class_method method call. Buf if there’s no explicit receiver, Ruby implicitly uses self as the receiver.

So we can remove the self. part:

my_class_method

And that still works!

Renaming

So this is looking closer to the style of declaration we want. Let’s clean it up by removing the spurious puts calls and renaming the method to has_many so it looks more familiar:

class Movie
  def self.has_many(name)
    puts "#{self} has many #{name}"
  end

  has_many :reviews
end

Notice the has_many takes a name argument, so calling has_many :reviews passes :reviews as the argument.

If we run it, we get:

Movie has many reviews

Notice the value of self is the Movie class object.

Define Method

OK, so the has_many method is being called during the class definition, so now what should the method do? Well, in Rails has_many dynamically generates a handful of methods for managing the association.

For example, in this case it would generate a reviews method that returns the reviews associated with the movie. And we’d call it like so:

movie = Movie.new
movie.reviews   # => undefined method

And if this were Rails, calling movie.reviews would return an array of reviews associated with that movie.

But if we try to run it, we get an “undefined method” error because there is no reviews instance method defined on our movie object. But we can define that method!

It would look something like this:

def self.has_many(name)
  puts "#{self} has many #{name}"

  def reviews
    puts "SELECT * FROM reviews WHERE..."
    puts "Returning reviews..."
    []
  end
end

But hard-coding the method name won’t work if we have another association, for example a genres association:

has_many :reviews
has_many :genres

To make this work, we need to dynamically define a method for each association: in this case a method named reviews and a method named genres. And we don’t know the names of those methods until runtime—until the class is being defined. So we need to define those method dynamically, on the fly.

Let’s start with the reviews method:

has_many :reviews

To do that, we can use define_method:

def self.has_many(name)
  puts "#{self} has many #{name}"

  define_method(name) do
    puts "SELECT * FROM #{name} WHERE..."
    puts "Returning #{name}..."
    []
  end
end

define_method takes as its argument the name of the method to generate and the body of the block becomes the body of the method. define_method always defines an instance method in the receiver, which in this case is the Movie object. So we’ll end up with a reviews instance method in the Movie class.

Now when we run it, we see that the reviews method got defined because we see the output of calling that method:

Movie has many reviews
SELECT * FROM reviews WHERE...
Returning reviews...

In fact, we can call reviews multiple times:

movie.reviews
movie.reviews

And when we run it, we see that the method is defined once but run multiple times:

Movie has many reviews
SELECT * FROM reviews WHERE...
Returning reviews...
SELECT * FROM reviews WHERE...
Returning reviews...

And because we’re defining the has_many method dynamically, we can now declare multiple associations:

class Movie < ActiveRecord::Base
  has_many :reviews
  has_many :genres
end

movie.reviews
movie.genres

Now two methods are dynamically defined as the class is being defined, and we can call those methods:

Movie has many reviews
Movie has many genres
SELECT * FROM reviews WHERE...
Returning reviews...
SELECT * FROM genres WHERE...
Returning genres...

So now our has_many method is dynamically defining methods based on the name of the association!

This is pretty cool, but it currently only works for movies. We’d like to share the has_many method across multiple classes. And we can do that using inheritance.

Class Method Inheritance

To share the has_many method using inheritance, first we’ll define it in class named Base inside a module named ActiveRecord (just so it’s similar to Rails):

module ActiveRecord
  class Base
    def self.has_many(name)
      puts "#{self} has many #{name}"
      define_method(name) do
        puts "SELECT * FROM #{name}..."
        puts "Returning #{name}..."
      end
    end
  end
end

Then the Movie class can inherit from the ActiveRecord::Base class:

class Movie < ActiveRecord::Base
  has_many :reviews
  has_many :genres
end

And everything works just as it did before:

Movie has many reviews
Movie has many genres
SELECT * FROM reviews WHERE...
Returning reviews...
SELECT * FROM genres WHERE...
Returning genres...

Notice that the value of self is the Movie class.

And now that we’re using inheritance, we can define a new subclass named Project that has many tasks:

class Project < ActiveRecord::Base
  has_many :tasks
end

project = Project.new
project.tasks

And running that gives us:

Project has many tasks
SELECT * FROM tasks WHERE...
Returning tasks...

Notice that the value of self is the Project class.

Summary

So now we’ve come full circle and implemented the two models we wanted with has_many declarations:

class Movie < ActiveRecord::Base
  has_many :reviews
  has_many :genres
end

class Project < ActiveRecord::Base
  has_many :tasks
end

Now, Rails does a whole bunch more stuff under the hood with these declarations, but hopefully that helps demystify these class-level declarations, sometimes called “macros”.

Just remember: there’s nothing special or magical about these methods. They’re just regular Ruby methods that generate code!

Source Code

Learn Ruby Today!

If you're using Rails and it feels too magical or you struggle with Ruby concepts, check out our in-depth Ruby course. You'll learn how to design object-oriented programs the Ruby way, deepen your understanding of the Ruby programming language, and demystify the "magic" of Ruby on Rails!

Ruby Course