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.

Example Anti-Pattern

@post = Post.find(params[:id])
@post_date = @post.body.posted_on

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:

class Post < ActiveRecord::Base
  has_one :body
end

class Body < ActiveRecord::Base
  belongs_to :post
end

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?

@address = @invoice.client.preferences.billing_address

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.

Delegate

Rails (via ActiveSupport) has a great little helper to solve our problem: delegate. Let’s see how we fix the “post” example above:

class Post < ActiveRecord::Base
  has_one :body
  delegate :posted_on, to: :body
end

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 @post.body.posted_on

Here’s how we solve the other example:

class Invoice < ActiveRecord::Base
  belongs_to :client
  delegate :billing_address, to: :client
end

class Client < ActiveRecord::Base
  has_many :invoices

  def billing_address
    preferences.fetch(:billing_address, nil)
  end
end

Now we can call @invoice.billing_address and Rails will handle the details.

Thinking Ahead

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.

Happy delegating!

Hi there, I'm Jon.

Writer. Musician. Adventurer. Nerd.

Purveyor of GIFs and dad jokes.