Original article Rails Design Patterns: Presenter & Service Objects
Why do we need to use design patterns?
The problem is that the structure of Rails, Model-View-Controller shows the basic structure for knowing where to write code.
But still not enough, the View will become large and full of logic when general purposes only represent information.
The controller contains details beyond what to do in the controller according to its basic task.
What is the solution?
In this article there are 2 solutions to the above problem as design patterns.
- Use the presenter pattern
- Use the service object pattern
Not everyone agrees on how to implement it, but in the article there will be a version to be functional. Let’s explore those patterns.
Use Presenter in Rails
Views to represent information mean HTML, CSS, and ERB (Embedded Ruby) files. Note that there should not be any ActiveRecord
queries in the views.
All logic must be removed from the view if it wants to be clean and easy to work with. (The logic here means the if
and the operator ?:
Now the solution to processing logic in that view is to use helpers.
Helpers are useful when we have a global format method that can be used in a number of views.
Example: Performing markdown format, displaying dates with a certain format, deleting specific words from text, etc.
How to use
We can put the code in the app/helpers
directory and in the date_helper.rb
file.
1 2 3 4 5 6 | <span class="token keyword">module</span> <span class="token constant">DateHelper</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">display_date_only_month_year</span></span> date date <span class="token punctuation">.</span> strftime <span class="token string">"%B %Y"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Advice
Always pass input to the helpers method as arguments instead of relying on instant variables. Because this will help avoid a lot of trouble
The Helpers method has a special limit if it is used for all purposes in the views. Because helpers tend to be constructive and lack of organization.
Replaces complex formatting methods and conditions
Imagine yourself having a view like this
1 2 3 4 5 | <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> p</span> <span class="token punctuation">></span></span> Post title: <span class="token erb language-erb"><span class="token delimiter punctuation"><%=</span> post <span class="token punctuation">.</span> title <span class="token punctuation">.</span> gsub <span class="token string">"forbidden word"</span> <span class="token punctuation">,</span> <span class="token string">""</span> <span class="token delimiter punctuation">%></span></span> <span class="token erb language-erb"><span class="token delimiter punctuation"><%=</span> link_to <span class="token string">"Read post"</span> <span class="token punctuation">,</span> post <span class="token punctuation">,</span> <span class="token keyword">class</span> <span class="token punctuation">:</span> <span class="token string">"w-75 p-3 text- <span class="token interpolation"><span class="token comment">#{post.draft? ? "orange" : "green"}</span></span> border- <span class="token interpolation"><span class="token comment">#{post.draft? ? "orange" : "green"}</span></span> "</span> <span class="token delimiter punctuation">%></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> p</span> <span class="token punctuation">></span></span> |
The above code is short, right? But when reading the code, it is complicated with operators and code iteration. Bad practice
Let’s create a presenter class to deal with the following
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token keyword">class</span> <span class="token class-name">PostPresenter</span> attr_reader <span class="token symbol">:post</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">initialize</span></span> post <span class="token variable">@post</span> <span class="token operator">=</span> post <span class="token keyword">end</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">title_without_forbidden_words</span></span> post <span class="token punctuation">.</span> title <span class="token punctuation">.</span> gsub <span class="token string">"forbidden word"</span> <span class="token punctuation">,</span> <span class="token string">""</span> <span class="token keyword">end</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">css_color</span></span> post <span class="token punctuation">.</span> draft <span class="token operator">?</span> <span class="token operator">?</span> <span class="token string">"orange"</span> <span class="token punctuation">:</span> <span class="token string">"green"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Save the above code in the directory app/presenters/post_presenter.rb
, create the presenters
directory if not already.
1 2 3 4 5 6 | <span class="token erb language-erb"><span class="token delimiter punctuation"><%</span> presenter <span class="token operator">=</span> <span class="token constant">PostPresenter</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> post <span class="token delimiter punctuation">%></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> p</span> <span class="token punctuation">></span></span> Post title: <span class="token erb language-erb"><span class="token delimiter punctuation"><%=</span> presenter <span class="token punctuation">.</span> title_without_forbidden_words <span class="token delimiter punctuation">%></span></span> <span class="token erb language-erb"><span class="token delimiter punctuation"><%=</span> link_to <span class="token string">"Read post"</span> <span class="token punctuation">,</span> post <span class="token punctuation">,</span> <span class="token keyword">class</span> <span class="token punctuation">:</span> <span class="token string">"w-75 p-3 text- <span class="token interpolation"><span class="token comment">#{presenter.css_color}</span></span> border- <span class="token interpolation"><span class="token comment">#{presenter.css_color}</span></span> "</span> <span class="token delimiter punctuation">%></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> p</span> <span class="token punctuation">></span></span> |
The advantages here are:
- Delete all logic from the view
- Give meaningful names to the format and decision operators.
- Reuse this class in other views without repeating code
Here’s how to use the Rails presenter
Service Subjects usage
The controller should only contain the to-do parts, they should not contain how to send a Tweet, charge a client fee or create a PDF file.
These handles should authorize a service object.
A service object is a Ruby module that encapsulates the logic to complete an action.
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">module</span> <span class="token constant">TwitterSerivce</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span> <span class="token punctuation">.</span> <span class="token function">send_welcome_message</span></span> twitter_handle client <span class="token punctuation">.</span> update <span class="token string">"@{twiter_handle} welcome to 'Dev studio', we hope you enjoy our music!"</span> <span class="token keyword">end</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token keyword">self</span> <span class="token punctuation">.</span> <span class="token function">client</span></span> <span class="token variable">@client</span> <span class="token operator">||</span> <span class="token operator">=</span> <span class="token constant">Twitter</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">REST</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Client</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token keyword">do</span> <span class="token operator">|</span> config <span class="token operator">|</span> config <span class="token punctuation">.</span> consumer_key <span class="token operator">=</span> <span class="token string">"..."</span> config <span class="token punctuation">.</span> consumer_secret <span class="token operator">=</span> <span class="token string">"..."</span> config <span class="token punctuation">.</span> access_token <span class="token operator">=</span> <span class="token string">"..."</span> config <span class="token punctuation">.</span> access_token_secret <span class="token operator">=</span> <span class="token string">"..."</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
For convenience save the code under the app/services
directory and the filename twitter_service.rb
.
Why should we use the above method? Since Rails autoloads all from the app/
directory, the above code will be available in the controllers.
In the controller we can use the following
1 2 3 4 5 6 | <span class="token keyword">class</span> <span class="token class-name">UsersController</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">create</span></span> <span class="token constant">TwitterService</span> <span class="token punctuation">.</span> send_welcome_message user <span class="token punctuation">.</span> twitter_handle <span class="token keyword">end</span> <span class="token keyword">end</span> |
This is the behavior of the service object.
Conclude
In the article, we have introduced two Rails patterns to help us improve the quality of code in the project. Please apply it now.
Thank you for reading