SERIES: ANTI PATTERNS AND HOW TO REFACTOR IN RUBY ON RAILS: DELEGATE METHOD

By: Saurav

2018-02-07 21:06:00 UTC

Use Delegation: Follow the law of demeter

An incredibly powerful feature of Ruby on Rails is Active Record associations, which are incredibly easy to set up, configure, and use. This ease of use allows you to dive deep down and across associations, particularly in views. However, while this functionality is powerful, it can make refactoring tedious and error-prone.

Let's say you have set up your associations as follow:

class Address < ActiveRecord::Base
  belongs_to :customer
end
class Customer < ActiveRecord::Base
  has_one :address
  has_many :invoices
end
class Invoice < ActiveRecord::Base
  belongs_to :customer
end

In the view, now you can access nested associated attributes as follow:

<%= @invoice.customer.name %>
<%= @invoice.customer.address.street %>
<%= @invoice.customer.address.city %>,
<%= @invoice.customer.address.state %>
<%= @invoice.customer.address.zip_code %>

For proper encapsulation, the invoice should not reach across the customer object to the street attribute of the
address object. Because if, for example, in the future your application were to change so that a customer has both a billing address and a shipping address, every place in your code that reached across these objects to retrieve the street would break and
would need to change

To avoid the problem just described, it’s important to follow the Law of Demeter, also known as the Principle of Least Knowledge.
an object can call methods on a related object but that it should not reach through that object to call a method on a third related object.

In Rails, this could be summed up as “use only one dot.”

For example, @invoice.customer.name breaks the Law of Demeter, but @invoice.customer_name does not.
To follow the Law of Demeter, you could rewrite the code above as follows:

class Address < ActiveRecord::Base
  belongs_to :customer
end

class Customer < ActiveRecord::Base
  has_one :address
  has_many :invoices
  def street
    address.street
 end
 def city
  address.city
 end
 def state
  address.state
 end
 def zip_code
  address.zip_code
 end
end


class Invoice < ActiveRecord::Base
  belongs_to :customer
  def customer_name
   customer.name
 end
  def customer_street
    customer.street
   end

  def customer_city
    customer.city
  end
  def customer_state
    customer.state
  end
  def customer_zip_code
   customer.zip_code
  end
end

And you could change the view code to the following:

<%= @invoice.customer_name %>
<%= @invoice.customer_street %>
<%= @invoice.customer_city %>,
<%= @invoice.customer_state %>
<%= @invoice.customer_zip_code %>

The downside to this approach is that the classes have been littered with many small wrapper methods. If things were to change, now all of these wrapper methods would need to be maintained. And while this will likely be considerably less work than
changing hundreds of references to invoice.customer.address.street throughout your code, it’s still an annoyance that would be nice to avoid.

In addition, your public interface on Invoice has been polluted by methods that arguably have nothing to do with the rest of your interface for invoices. This is a general disadvantage of Law of Demeter, and it is not particularly specific to Ruby on
Rails.

Fortunately, Ruby on Rails includes a function that addresses the first concern. This method is the class-level delegate method. This method provides a shortcut for indicating that one or more methods that will be created on your object are actually
provided by a related object. Using this delegate method, you can rewrite your example like this:

class Address < ActiveRecord::Base
  belongs_to :customer
end
class Customer < ActiveRecord::Base
  has_one :address
  has_many :invoices
  delegate :street, :city, :state, :zip_code, :to => :address
end
class Invoice < ActiveRecord::Base
  belongs_to :customer
  delegate :name,
    :street,
    :city,
    :state,
    :zip_code,
    :to => :customer,
    :prefix => true
end


#prefix - Prefixes the new method with the target name or a custom prefix

In this situation, you don’t have to change your view code; the methods are exposed just as they were before:

<%= @invoice.customer_name %>
<%= @invoice.customer_street %>
<%= @invoice.customer_city %>,
<%= @invoice.customer_state %>
<%= @invoice.customer_zip_code %>

Note All credit goes to the book I am reading: Rails Antipattern by Chad Pytel and Tammer Saleh

Owned & Maintained by Saurav Prakash

If you like what you see, you can help me cover server costs or buy me a cup of coffee though donation :)