Design Patterns: Law of Demeter with Rails
Developing better Rails applications
Being a good rails developer is not just to know some basic stuff like creating model, controller and migration. Almost every project tends to become more complicated and that’s why it’s necessary to know design patterns to avoid complication of code and time waste while refactoring.
Here we’ll talk about Law Of Demeter, very powerful and flexible pattern. Let’s see what is that Law and how it simplifies developer’s life.
I suppose you’re already familiar with ActiveRecord, brilliant tool. If you have Employee, which belongs to Company, and Company has Location, you will easily access methods of related objects from Employee instance:
@employee.company.name
@employee.company.location.street
Simple as that. Very popular approach used by many rails developers. But there are number of reasons why this code is not good:
- What if you will need to change name ‘location’ to ‘address’? In that case you must change it everywhere in code one by one.
- What if some of related objects are nil? NoMethodError guaranteed.
- And do you like that long line of code after all? I’m sure you don’t. It’s called ‘Multi dot syndrome’.
The alternative
And here the Law Of Demeter comes to play, which says:
Use only one dot.
and according to that advise, code above should look like this:
@employee.company_name
@employee.company_street
Diving deeper
We have 3 models and relationships between them: Company, Employee and Location
class Employee < ActiveRecord::Base
belongs_to :company
endclass Company < ActiveRecord::Base
has_one :location
has_many :employees
endclass Location < ActiveRecord::Base
belongs_to :company
end
As we said, usage of several dots violates the Law. We need some short methods. Rails has very powerful thing for that called Delegation:
Simply say, delegation allows you to use methods of one object from another.
Something like this:
# instead of @employee.company.name method
<%= @employee.company_name %>
To achieve that, add delegate method to Employee model:
class Employee < ActiveRecord::Base
belongs_to :company delegate :name, to: :company, prefix: 'company'
end
Here we’ve specified method name that we want to use (:name), name of the class where we want to use method from (:company), and a prefix to use :name method like company_name.
Also you can specify prefix: true. In that case delegate will look for a class that we’ve passed to :to hash and use that name as a prefix. But I prefer to specify something directly instead of just true and i’ll explain why little later.
With this approach you can use all methods from :company in Employee instance. If we will create some new method :phone in Company and pass it to delegate in Employee class:
class Employee < ActiveRecord::Base
belongs_to :company delegate :name, :phone, to: :company, prefix: 'company'
end
we will be able to use it exactly the same way as the :name
<%= @employee.company_name %>
<%= @employee.company_phone %>
Brilliant, isn’t it ?
Company’s location info
Besides company’s name, we also retrieving company location info. To achieve that we need to delegate Location’s methods in Company model:
class Company < ActiveRecord::Base
has_one :location
has_many :employees delegate :city, :street, to: :location
end
Now Company can use this methods directly. But as you remember, we need to use these methods from Employee instance, and that’s why we should delegate :city, :street, :zip_code methods also in Employee model:
class Employee < ActiveRecord::Base
belongs_to :company delegate :name, :city, :street, to: :company, prefix: 'company'
end
After that you can easily use all these methods from Employee instance. This is how code looks like after refactoring:
<%= @employee.company_name %>,
<%= @employee.company_city %>,
<%= @employee.company_street %>
Why i not recommend prefix: true
We know prefix: true gets class name provided to :to hash and uses it as a prefix. But what if I want to change name from Company to Cmpn? In that case prefix: true will use ‘cpmn’ as a prefix and methods will look like this:
@employee.cmpn_name
and that’s why NoMethodError will be raised. This is why I prefer to use something like prefix: ‘company’. Now if Company model will be renamed, we still will be able to use our methods without change.
What if some method name will be changed?
If you decided to change Location’s method street to str, NoMethodError will be raised. Solution in that case is to create new method street or an alias in Location model:
class Location < ActiveRecord::Base
belongs_to :company def street
str
end # or alias method
# alias_method :street, :str
end
and delegate will look for :street, but will get data from :str method.
What if some of related objects is nil?
If Company or Location object is nil, we will have NoMethodError raised. We can avoid that by specifying allow_nil: true in delegate method:
delegate ..., allow_nil: true
Conclusion
As you can see, Law Of Demeter and Delegation are very easy, powerful and flexible. In my opinion they must be used in every application, doesn’t matter how small or big apps are.
Good luck and never violate Law of Demeter!