Delegate
This article will walk you through the process of using delegate method in rails. Along the way, we will also discuss the design principle behind this method.
Table of Contents
What is delegate?
delegate
is a method in rails that provides a delegate class method to easily expose contained objects’ public methods as your own. In other words, through delegation you can use other model’s public methods directly.
Why do we need to use delegate?
There is a design principle in programming called “the law of demeter”, also called “the principle of least knowledge”. This principle implies that an entity should only talk to its close friends. To put it another way, an object should not call methods through another object. This principle reduces dependencies and helps build components that are loosely coupled for code reuse, easier maintenance, and testability.
For example, the code below violates the law of demeter:
post = Post.first
post.user.email #=> email@domain.com
You should treat email
as an own method of post
instead.
post = Post.first
post.user_email #=> email@domain.com
#or
post.email #=> email@domain.com
For the codes above to work, we need to define a method called user_email
or email
in the post model. However, if we do this, the model will soon become crowded with this type of method. Luckily, there is a built-in rails method called delegate that can handle this type of situation.
How to use delegate?
To adhere to the law of demeter we should avoid using post.user.email
. We need to use delegate to fix it.
To use delegate, you just need to add the delegate method on your model. In our case the post model.
#app/models/post.rb
class Post < ApplicationRecord
#...
+ delegate :email, to: :user
mount_uploader :image, ImageUploader
#...
end
You can now use the email method directly.
<!--app/views/posts/index.html.erb-->
<!--...-->
<table>
<!--...-->
<% @posts.each do |post| %>
<!--...-->
<td data-controller="clipboard">
- <span data-clipboard-target="email"><%= post.user.email %></span>
+ <span data-clipboard-target="email"><%= post.email %></span>
<button data-action="click->clipboard#copy">copy</button>
</td>
<!--...-->
<% end %>
</table>
<!--...-->
There are also some options you can use in delegation
:to | Specifies the target object |
:prefix | Prefixes the new method with the target name or a custom prefix, this is optional |
:allow_nil | if set to true, prevents a NoMethodError to be raised. By default, this is false |
You can use the prefix
option to make the method more readable.
#app/models/post.rb
class Post < ApplicationRecord
#...
- delegate :email, to: :user
+ delegate :email, to: :user, prefix: :user
mount_uploader :image, ImageUploader
#...
end
<!--app/views/posts/index.html.erb-->
<!--...-->
<table>
<!--...-->
<% @posts.each do |post| %>
<!--...-->
<td data-controller="clipboard">
- <span data-clipboard-target="email"><%= post.email %></span>
+ <span data-clipboard-target="email"><%= post.user_email %></span>
<button data-action="click->clipboard#copy">copy</button>
</td>
<!--...-->
<% end %>
</table>
<!--...-->
The prefix
gave us more context on how email method is related to post.