January 26, 2014
I like Ruby. It has a way of appearing clean and simple, while still being expressive. Due to its typing system you can bend it in a variety of ways to do certain things that would be very complicated in other languages, or make you write an entire compiler. I’m talking, of course, about Domain Specific Languages, or DSLs. If you’ve ever used a popular Ruby library (like Rails, or RSpec, or anything else) then you’ve used a DSL. Rails in particular has a couple different ones:
# Routes
# config/routes.rb
DerptyDerp::Application.routes.draw do
resources :users
resources :dinguses, only: [:new, :show]
end
# ActiveRecord
# app/models/user.rb
class User < ActiveRecord::Base
validates :email, presence: true, uniqueness: true
has_one :dingus, dependent: :destroy
end
And so on. At first glance, the code that allows this can be intimidating, especially when it’s been broken up and refactored across 20 different classes and modules. Fortunately, there are probably only about 10 things that you really need to know to read along, and to make them yourself.
Did you know that you can execute code right from within a class declaration? Well, you can. Try it!
class Derp
def self.can_eat_sandwiches
puts "I LOVE CATS"
end
can_eat_sandwiches
end
When that class is read by the interpreter, it’ll execute the can_eat_sandwiches
method and print the non-sequitur to the console. This’ll even work through inheritance.
class Herp < Derp
can_eat_sandwiches
end
And you’ll have a wonderful message printed out twice. This technique can be used to do more than just clutter up a log file too! For example, in Rails when you setup validation, all you’re doing is calling another method.
Sometimes you want to add functionality to a class without locking it into a long inheritance chain. Ruby uses Modules to accomplish this, so you can pull your methods into a separate file to include them at will. There’s a catch though before you dive in. Traditionally, you’d include a module like this:
module TheHerppening
def can_eat_sandwiches
end
end
class Sandwichable
include TheHerppening
end
If you know how modules work, you might’ve noticed that will only add can_eat_sandwiches
to instances of Sandwichable
, not to Sandwichable
itself. Which means that calling can_eat_sandwiches
directly in the class won’t work. But fear not! Ruby can be molded in many dark and fearsome ways. Rather than using include
, you can use extend
to add methods to a class.
class Sandwichable
extend TheHerppening
can_eat_sandwiches
end
Aaaaand we’re back in business. But, say you wanted to have some methods that needed to be on instances, and some for the class? Can you mix both? Sure. Just wrap them in different modules and add them with the appropriate calls. There’s even a pattern to follow for naming the modules: ClassMethods
and InstanceMethods
:
module TheHerppening
module ClassMethods
def can_eat_sandwiches
puts 'DING'
end
end
module InstanceMethods
def eat_the_sandwich
puts 'I wish I could marry a sandwich.'
end
end
end
They can even be in the same file. As a side note, this is also how you can do namespacing in Ruby. Including these can be a little tedious if you have to add this to lots of classes.
class Sandwichable
extend TheHerppening::ClassMethods
include TheHerppening::InstanceMethods
can_eat_sandwiches
end
Not to mention ugly. And if there’s one thing a good Ruby coder can’t stand, it’s ugly code. Let’s fix that.
Modules (and classes) have all kinds of meta-programming capabilities built in. I encourage you to go and peruse them at your leisure. What we’ll focus on here is the [included](http://ruby-doc.org/core-2.1.0/Module.html#method-i-included)
method.
module Dingus
class << self
def included(base)
base.send :include, Dingus::InstanceMethods
base.extend Dingus::ClassMethods
end
end
end
All of a sudden, your class now has all of the extra stuff from the Dingus
module inside it.
That might be jumping ahead a few steps, what with the class << self
and all. Don’t forget that in Ruby, everything is a class, even modules. Conceptually it might be easier to understand if you look at Module
’s ancestors:
> Module.ancestors
=> [Module, Object, PP::ObjectMixin, Kernel, BasicObject]
So, a Module
is just a subclass of Object
and can be treated as such. Whoa. Specifically, the class << self
allows you to put class level methods on the module (you can also do this to a class as an alternative to def self.whatevers
).
module Dingus
class << self
def say_hi
puts 'HI'
end
end
end
Dingus.say_hi # HI
And, included
is called when that module is included in a class. This means you can set off a chain reaction of includes with just a single one.
This is just the start. Ruby gives you enough metaprogramming rope to build a hammock and then hang yourself with the leftovers. We’ll get into some of the other ways to do this later on, including blocks, eval, and the infamous method-missing. I encourage you to start digging into some code, and even trying a little of this yourself. Once you get a few reps in, understanding how DSLs work becomes much easier.
Written by Scott Williams who lives and works in sunny Phoenix, AZ. Twitter is also a place.