Rails Internals: named_scope Refactored
Got a question or comment about this article? Find us on twitter and let us know!.
You've probably heard that reading someone else's code is one way to learn more Ruby, and it's true. If you're a Rails developer, you already have a large quantity of reading material at your disposal: the Rails source itself. Reading the Rails source code is a great way to learn about Ruby idioms and techniques that you may not be using in your own applications.
Not familiar with named_scope? Check the API documentation or read about it in the official Rails guide.
Some Rails code is bread-and-butter Ruby, that most developers can readily understand. Other parts use more advanced idioms or a more densely-packed style, which might dissuade the casual developer from being able to understand the code. That's a shame, because valuable concepts and techniques can go unnoticed and unlearned.
We're going to take a look at the implementation of named_scope, a very popular feature of ActiveRecord that first appeared in Rails 2.1.
1. What is Refactoring?
Martin Fowler, author of the well-known Refactoring book, identifies many kinds of refactoring patterns. One of the most common techniques - extract method - can help simplify a method that's trying to do too much, or whenever the _intent_ of the code isn't clear enough.
To demonstrate, we're going to take a look at the implementation of one of the most popular features in Rails 2.1: named_scope.
2. The 10-Second Rule
No, I'm not talking about the bagel you dropped on the floor. Take a look at the following code. It's taken directly from the Rails 2.1 source code. Can you understand it in 10 seconds or less?
|
|
Unless you're comfortable with some advanced Ruby concepts, this code may be pretty hard to understand.
There are several things going on here that make this code harder to read than it ought to be. Let's walk through them one at a time. We'll refactor as we go, leaving us with code that will behave the same, but will be make the named_scope method more readable and its intent more transparent.
The method starts innocently enough, by coercing the name parameter into a symbol.
Then we go over the cliff pretty fast. Line 5 looks like it's going to assign a key-value pair into a hash named scopes. The key for the pair is name, but the value is... well, something complicated:
|
|
It's a lambda that takes multiple parameters, and which will return a new Scope object. We don't know yet what the Scope class does, but we don't need to worry about that for now. Furthermore, creating this Scope instance seems to require three parameters, although it may not be immediately obvious.
The first parameter is simply the parent_scope value that was received by the lambda. The second, however, requires a case statement to determine, and the author of this code has chosen to put the case statement inline with the rest of the code. The third parameter is the &block parameter that the named_scope method originally received.
After some digging into the Scope class, and researching where the scopes hash is used, I determined that the scopes hash maps the name of your named scope to a lambda expression that will be executed later.
3. Refactoring, Part 1
Let's refactor this code segment out to a well-named method that more clearly describes the author's original intent.
|
Now we can simplify the original code a little bit:
|
|
Wow, that helped a lot. Less code, more transparency. It feels a lot better already.
4. Refactoring, Part 2
We now have less code in the named_scope method. But the code that remains still feels a bit funny. Ruby methods should contain code at the same level of abstraction. The nice-looking call to save away the scope definition is followed several lines of very detailed-looking code.
Let's take a closer look at the remaining code:
|
There are actually two advanced Ruby idioms in use here. Suffice to say, this code will generate a new method in your model class. The name of this new method is made to be the same as your named scope; and the implementation is actually quite simple: it first finds the appropriate scope definition that had previously been saved away, and executes it.
Let's again factor this out to a well-named method:
|
And now, the named_scope method has been reduced to this:
|
|
We can even do away with that reassignment of the name variable as well, and perform the conversion on the fly:
|
|
5. What Did We Learn?
Imagine if our final version of the named_scope was the _first_ version you saw. You'd probably have a much easier time understanding how it works because there are two methods, each descriptively named, that provide insight into their intent.
Refactoring is great way to learn code that you don't understand. Begin slowly, and work to understand what the code does. As you gain understanding, you may find that extracting out the more complicated segments into well-named methods will bring a level of clarity that you did not have when you started.
For a complete introduction to the practice of refactoring, we recommend Martin Fowler's seminal work, Refactoring: Improving the Design of Existing Code.
