Foreword
In this article I will explain the delegate
method in Ruby on Rails, the concept, the reason to use it and the proper usage through small examples.
content
What is delegate?
According to the official API description:
Provides a delegate class method to easily expose contained objects' public methods as your own.
Provide the object with a delegate
class method to easily call the public methods of other objects just like that object itself.
1 2 | <span class="token function">delegate</span> <span class="token punctuation">(</span> <span class="token operator">*</span> methods <span class="token punctuation">,</span> to <span class="token punctuation">:</span> <span class="token keyword">nil</span> <span class="token punctuation">,</span> prefix <span class="token punctuation">:</span> <span class="token keyword">nil</span> <span class="token punctuation">,</span> allow_nil <span class="token punctuation">:</span> <span class="token keyword">nil</span> <span class="token punctuation">)</span> |
Why should you use Delegate?
Demeter's law is a design principle for software development, especially object-oriented programs. This law is stated as follows:
Minimize the understanding of an object about the structure and properties of objects other than it (including children).
This makes the components in the system less dependent on each other, easier to pack and reuse.
The delegate
method in Rails helps us to apply this law by only allowing access to the necessary methods of the parent object and thereby, making access to their properties easier.
How to use Delegate?
For example, we have 2 ActiveRecord models that have has_many
relationship as follows:
1 2 3 4 5 6 7 8 | <span class="token keyword">class</span> <span class="token class-name">GiftBox</span> <span class="token operator"><</span> <span class="token constant">ActiveRecord</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Base</span> has_many <span class="token symbol">:kit_kats</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">KitKat</span> <span class="token operator"><</span> <span class="token constant">ActiveRecord</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Base</span> belongs_to <span class="token symbol">:gift_box</span> <span class="token keyword">end</span> |
Open up the rails console
and see what these two properties have:
1 2 3 4 5 | <span class="token punctuation">[</span> <span class="token number">3</span> <span class="token punctuation">]</span> <span class="token function">pry</span> <span class="token punctuation">(</span> main <span class="token punctuation">)</span> <span class="token operator">></span> <span class="token constant">GiftBox</span> <span class="token punctuation">.</span> column_names <span class="token operator">=</span> <span class="token operator">></span> <span class="token punctuation">[</span> “id” <span class="token punctuation">,</span> “name” <span class="token punctuation">,</span> “description” <span class="token punctuation">,</span> “image_url” <span class="token punctuation">,</span> “created_at” <span class="token punctuation">,</span> “updated_at” <span class="token punctuation">]</span> <span class="token punctuation">[</span> <span class="token number">4</span> <span class="token punctuation">]</span> <span class="token function">pry</span> <span class="token punctuation">(</span> main <span class="token punctuation">)</span> <span class="token operator">></span> <span class="token constant">KitKat</span> <span class="token punctuation">.</span> column_names <span class="token operator">=</span> <span class="token operator">></span> <span class="token punctuation">[</span> “id” <span class="token punctuation">,</span> “gift_box_id” <span class="token punctuation">,</span> “message” <span class="token punctuation">,</span> “created_at” <span class="token punctuation">,</span> “updated_at” <span class="token punctuation">]</span> |
Now we will get the name
and description
of GiftBox
through KitKat
, usually we will do the following:
1 2 3 4 5 6 | <span class="token punctuation">[</span> <span class="token number">5</span> <span class="token punctuation">]</span> <span class="token function">pry</span> <span class="token punctuation">(</span> main <span class="token punctuation">)</span> <span class="token operator">></span> kit_kat <span class="token operator">=</span> <span class="token constant">KitKat</span> <span class="token punctuation">.</span> <span class="token function">find</span> <span class="token punctuation">(</span> <span class="token number">13</span> <span class="token punctuation">)</span> <span class="token punctuation">[</span> <span class="token number">6</span> <span class="token punctuation">]</span> <span class="token function">pry</span> <span class="token punctuation">(</span> main <span class="token punctuation">)</span> <span class="token operator">></span> kit_kat <span class="token punctuation">.</span> gift_box <span class="token punctuation">.</span> name <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">"Rainy"</span> <span class="token punctuation">[</span> <span class="token number">7</span> <span class="token punctuation">]</span> <span class="token function">pry</span> <span class="token punctuation">(</span> main <span class="token punctuation">)</span> <span class="token operator">></span> kit_kat <span class="token punctuation">.</span> gift_box <span class="token punctuation">.</span> description <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">"For Valentine's Day"</span> |
So we have obtained the name
and description
through the relationship with the GiftBox
But if we use delegate
method, we can get name
and description
more easily and conveniently. Please revise the KitKat model a bit.
1 2 3 4 5 6 | <span class="token keyword">class</span> <span class="token class-name">KitKat</span> <span class="token operator"><</span> <span class="token constant">ActiveRecord</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Base</span> belongs_to <span class="token symbol">:gift_box</span> <span class="token keyword">end</span> delegate <span class="token symbol">:name</span> <span class="token punctuation">,</span> <span class="token symbol">:description</span> <span class="token punctuation">,</span> to <span class="token punctuation">:</span> <span class="token symbol">:gift_box</span> |
By using delegates, I have made the name
and description
of GiftBox as KitKat methods already. From now on we can get them without having to call :gift_boxs
anymore by the following:
1 2 3 4 5 | <span class="token punctuation">[</span> <span class="token number">10</span> <span class="token punctuation">]</span> <span class="token function">pry</span> <span class="token punctuation">(</span> main <span class="token punctuation">)</span> <span class="token operator">></span> kit_kat <span class="token punctuation">.</span> name <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">"Rainy"</span> <span class="token punctuation">[</span> <span class="token number">11</span> <span class="token punctuation">]</span> <span class="token function">pry</span> <span class="token punctuation">(</span> main <span class="token punctuation">)</span> <span class="token operator">></span> kit_kat <span class="token punctuation">.</span> description <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">"For Valentine's Day"</span> |
:prefix
But what if the KitKat model itself has the name
method? At that time we need to use the option :prefix
:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">class</span> <span class="token class-name">KitKat</span> <span class="token operator"><</span> <span class="token constant">ApplicationRecord</span> belongs_to <span class="token symbol">:gift_box</span> delegate <span class="token symbol">:name</span> <span class="token punctuation">,</span> <span class="token symbol">:description</span> <span class="token punctuation">,</span> to <span class="token punctuation">:</span> <span class="token symbol">:gift_box</span> <span class="token punctuation">,</span> prefix <span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token keyword">def</span> name <span class="token string">"Matcha"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
We can then call the two method name
as follows:
1 2 3 4 5 | <span class="token punctuation">[</span> <span class="token number">10</span> <span class="token punctuation">]</span> <span class="token function">pry</span> <span class="token punctuation">(</span> main <span class="token punctuation">)</span> <span class="token operator">></span> kit_kat <span class="token punctuation">.</span> name <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">"Matcha"</span> <span class="token punctuation">[</span> <span class="token number">11</span> <span class="token punctuation">]</span> <span class="token function">pry</span> <span class="token punctuation">(</span> main <span class="token punctuation">)</span> <span class="token operator">></span> kit_kat <span class="token punctuation">.</span> gift_box_name <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">"Rainy"</span> |
We can custom name the custom prefix, such as prefix: :show
1 2 3 | <span class="token punctuation">[</span> <span class="token number">11</span> <span class="token punctuation">]</span> <span class="token function">pry</span> <span class="token punctuation">(</span> main <span class="token punctuation">)</span> <span class="token operator">></span> kit_kat <span class="token punctuation">.</span> show_name <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">"Rainy"</span> |
:allow_nil
If the object in :to
has a value of nil
, it will throw an Exception:
1 2 3 | <span class="token punctuation">[</span> <span class="token number">34</span> <span class="token punctuation">]</span> <span class="token function">pry</span> <span class="token punctuation">(</span> main <span class="token punctuation">)</span> <span class="token operator">></span> <span class="token constant">KitKat</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">.</span> name <span class="token builtin">Module</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">DelegationError</span> <span class="token punctuation">:</span> <span class="token constant">KitKat</span> <span class="token comment">#name delegated to gift_box.name, but gift_box is nil: #<KitKat id: nil, gift_box_id: nil, description: nil, created_at: nil, updated_at: nil></span> |
To avoid returning Module::DelegationError
, we can add the allow_nil: true
option to the delegate
:
1 2 3 4 5 6 7 | <span class="token keyword">class</span> <span class="token class-name">KitKat</span> <span class="token operator"><</span> <span class="token constant">ApplicationRecord</span> belongs_to <span class="token symbol">:gift_box</span> delegate <span class="token symbol">:name</span> <span class="token punctuation">,</span> <span class="token symbol">:description</span> <span class="token punctuation">,</span> to <span class="token punctuation">:</span> <span class="token symbol">:gift_box</span> <span class="token punctuation">,</span> allow_nil <span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token keyword">end</span> |
1 2 3 | <span class="token punctuation">[</span> <span class="token number">36</span> <span class="token punctuation">]</span> <span class="token function">pry</span> <span class="token punctuation">(</span> main <span class="token punctuation">)</span> <span class="token operator">></span> <span class="token constant">KitKat</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">.</span> name <span class="token operator">=</span> <span class="token operator">></span> <span class="token keyword">nil</span> |