2. Law of demeter and delegation

Let's take a look at this piece of code:

<% @posts.each do |post| %>
  <%= link_to post.user.email.split('@').first, user_path(post.user_id) %>

  <% post.comments.each do |comment| %>

    <%= link_to comment.user.email.split('@').first,  user_path(comment.user_id) %> <%= comment.text %>

  <% end %>

  ...

<% end %>

In this code above, let's specifically look at the following pieces of code:

post.user.email.split('@').first

...

comment.user.email.split('@').first

Notice how the code above all call attributes of another object.

For example, in post.user.email, email is an attribute of user, which is an attribute of post.

This violates the Law of Demeter.

The Law of Demeter

In the simplest terms, the Law of Demeter states that an object should not call methods through another object. It is also called the principle of least knowledge.

The reason and motive for this guideline is to keep the code maintainable and adaptable. By keeping an object from calling methods of another object, we keep the objects less dependent on other objects. This is related to the concept of loose coupling.

Loose coupling refers to code in which objects do not depend on each other and has little or no knowledge about other objects. Tightly coupled code can lead to unmaintainable code - if you change one piece of the code, it might lead another piece of the code base to break.

How can we abide by the law of demeter in Rails?

What we can do is create methods within the class that return the specific method of the other object we are trying to access. For example, in our Post.rb, we can write the following methods:

class Post < ActiveRecord::Base
  belongs_to :user
  has_many :comments, dependent: :destroy

  has_one_attached :photo

  def user_email
    user.email
  end
end

Now, instead of accessing the email of the user directly (post.user.email.split('@').first), we have created a method inside of the Post class that we can use to access the same data: post.user_email.

However, this can become repetitive, and fortunately Rails provides a convenient method to achieve this; the delegate method.

We can refactor the code like this:

In Post.rb:

class Post < ActiveRecord::Base
  belongs_to :user
  has_many :comments, dependent: :destroy

  has_one_attached :photo

  delegate :email, to: :user, prefix: true
end

In Comment.rb:

class Comment < ActiveRecord::Base
  belongs_to :user
  belongs_to :post

  delegate :email, to: :user, prefix: true
end

In index.html.erb:

<!--  link_to post.user.email.split('@').first, user_path(post.user_id) -->
<%= link_to post.user_email.split('@').first, user_path(post.user_id) %>

<!-- link_to comment.user.email.split('@').first  user_path(comment.user_id) -->
<%= link_to comment.user_email.split('@').first,  user_path(comment.user_id) %>

Setting prefix: true gives you a method like this: post.user_email. Setting prefix: false would result in a method without the prefix - post.email.

To make the code look even shorter, create a method in helper file like below:

def user_name(email)
  email.split('@').first
end

And replace the erb file as below:

<!--  link_to post.user_email.split('@').first, user_path(post.user_id) -->
<%= link_to username(post.user_email), user_path(post.user_id) %>

<!-- link_to comment.user_email.split('@').first  user_path(comment.user_id) -->
<%= link_to username(comment.user_email), user_path(comment.user_id) %>

By using delegate, we can follow the Law of Demeter, which will lead to a more maintanable codebase.

Extra Reading