Ruby Class Annotations Demystified
Saturday, 18 April 2015 · 13 min read · rubyIf you’re working with a framework (like Rails, Django, Sinatra, or Flask), learning to use it is just scratching the surface. Be rigorous. Go deeper. Learn how it works.
Let’s take a close look at real-world uses of metaprogramming.
Metaprogramming is the writing of computer programs with the ability to treat programs as their data. It means that a program could be designed to read, generate, analyse and/or transform other programs, and even modify itself while running.
We’ll deconstruct how Rails’ ActiveRecord creates methods on the fly in has_secure_token
and enum class annotations. By the end of this post, you’ll better understand the underlying mechanisms used by Rails and your favorite gems to create their class annotations.
Metaprogramming is one of my all-time favorite topics, so let’s get started!
Source code for Rails’ has_secure_token
annotation reproduced below:
Note that lib modules such as
SecureToken
are automatically loaded into Active Record. Below is an excerpt ofactive_record.rb
showing how modules are imported:
Also,
extend ActiveSupport::Concern
does what’s called the include-and-extend trick.
The first thing you’ll notice is module ClassMethods
. This module allows class annotations such as has_secure_token
to be imported.
Next, let’s take a closer look at has_secure_token
:
Whenever the has_secure_token
annotation is included like so:
The has_secure_token
method is executed within the scope of the class. It does two things:
- Using the native
Module#define_method
method (ha), we dynamically define a newregenerate_*
method under our class based on the attribute parameter passed into thehas_secure_token
annotation. It takes a method name string or symbol and aProc
,Method
, or anUnboundMethod
object. - We also define a new
before_create
filter for the current class.
As you can see, the class annotation generates new methods on the fly and modifies the class which it annotates as the code is being interpreted. Cool, huh?
Now, let’s see another example. Here’s an example usage of Rails’ enum
class annotation:
What!? I never defined those methods in my class, how did Rails know how to respond to .active!
and .archived?
when I never explicitly defined them?
The answer: Metaprogramming.
Let’s take a closer look. Source code for Rails’ enum
annotation reproduced below:
The code seems complex and the result feels magical, but in reality it’s quite simple. The enum
method does three things of note:
- For each
enum
annotation (definition), checks that the enum name doesn’t conflict with existing getter and setter method names.
Each
enum
annotation takes aname
string andvalues
symbol array of enum types.
- For each
enum
, defines a new attribute under the current class with namename
pointing to its enum value - For each
enum
AND for each of thatenum
’s valuevalue
, defines two methods:#{value}?
and#{value}!
. It also defines a new scopescope value, -> { where #{name} => #{value} }
.
Cool, huh? Metaprogramming is how most Ruby magic happens under the hood! Refer back to the enum
source and make sure you can understand how those methods are generated.
There’s a lot more to metaprogramming than what I’ve mentioned, such as method_missing
, class_eval
, instance_eval
, and hook methods (inherited
, included
, and son on.) Each have found its use in many Ruby frameworks and libraries.
If you’re interested to learn more about metaprogramming in Ruby, be sure to read the links below!
Additional reading:
📬 Get updates straight to your inbox.
Subscribe to my newsletter so you don't miss new content.