Delegation in Rails
After spending a year or two in Rails, I started to plateau a bit. I knew my code wasn’t clean, but didn’t know how to make it better. I began to ask more-senior Rails programmers about thoughts and ideas that helped them improve.
One of the more influential lessons I learned was about the Law of Demeter (and loose coupling). Let’s start with defining what it is, then anti-patterns, and an example on how Rails makes it easier to avoid some costly technical debt.
Law of Demeter
Here’s the best definition I’ve found for the Law of Demeter: “Principle of Least Knowledge: Only talk to your friends”
Basically, we want our models to have very little knowledge of the models it interacts with. “Loose coupling” means we can swap out models and/or make changes without requiring a complete re-write. You’ll see a bit later what can happen when your models are tightly coupled, but for now, let’s look at this bad example.
In this (contrived) example, we’re wanting to get the date for a blog post. We have a relationship between post and a body, like this:
So the Post’s body has the date we want, but we have to hop through Body to get there. This isn’t bad now, but will get bad once we try to refactor this. This is an example of tight coupling.
Ever seen (or written) something like this?
We’re taking the
@invoice we have, but we’re storing an address on the client, perhaps in a
preferences hash. If we ever want to tweak anything in the middle, we’re gonna have a bad time.
Rails (via ActiveSupport) has a great little helper to solve our problem:
delegate. Let’s see how we fix the “post” example above:
That’s it! Now we can call
posted_on directly on our
@post instance. Sprinkle
@post.posted_on wherever you need it, and if ever the delegation changes, just change the implementation in one spot. Much better than a massive find/replace on
Here’s how we solve the other example:
Now we can call
@invoice.billing_address and Rails will handle the details.
So how do you know when to implement
delegate in your own project? What’s the “code smell”?
The best way to catch it in the act it to be on the look out for multiple “dots”. If your object needs to reach across more than one other object to get its data, you’re probably violating the Law of Demeter. Instead, start by writing the method as you want it to be without worrying about implementation first.
For instance, if you wrote
@post.body.posted_on, an alarm should go off in your head: “Why am I asking another object for data that makes perfect sense for me to know myself?” Instead, write
@post.posted_on, then move down to the model layer to implement.
@invoice.client.preferences... “OH LAWD! No good.”
@invoice.billing_address – “Ahhhh. Much better.”
The more you get in the habbit of smelling out bad code like this, the faster you’ll get at implementing them right the first time. Staying ahead of technical debt like this will save you hours (sometimes days) of headache in the future.