When I started my internship at CodeMiner 42 last October, I felt overwhelmed by information. So much new stuff to learn, and so much old stuff that I was doing wrong and had to improve on. It’s been a great way to improve myself as a person and mostly as a developer.
Usually, my workflow would go along these lines:
- I’d get a task and think about how to do it;
- I’d do it;
- I’d ask one of my coworkers to make sure it’s all according to the convention;
- I’d submit it for code review to get shredded by other people;
- I’d address the code review and submit it again.
I repeated the last two steps for as long as necessary until the day came when one of my coworkers questioned why I did something the way I did. To which I replied:
“Well, I don’t really know why I did it this way. I only did it like that because Dieggo told me to” (Dieggo is the coworker I usually bother the most with questions.)
To which he responded something that stuck with me:
The whole purpose of me asking was to get you thinking and understanding what you’re doing, instead of just following orders.
Around the same time, there was an internship review in which several questions about my knowledge, on several topics, were asked, and a couple of them I had little to no knowledge on. Here I’ll be talking about the one that seemed the most important for me: Design Patterns.
I never paid attention to the design part of coding. Usually, I’d just worry about getting stuff done and making the code look pretty. By recently getting an early grasp on design patterns, though, I was able to understand why some things are done the way they are.
When I started exploring the subject, I learned about the Strategy Pattern and Observable Pattern, both of which I could understand with respect to why they existed and what would I use them for, but found no application of them in my day to day activities with Ruby On Rails.
Then I, after understanding what a design pattern was, decided to look for the ones that are more directly used with Ruby on Rails. But such content proved to be surprisingly scarce. The first design pattern I found I recognized by name: the Decorator Pattern.
Lo and behold: the Decorator Pattern
“I have used this already,” I thought to myself when I read the name and realized I’d used it on previous projects.
When I started studying decorators, I understood it as a way to remove logic from the views — after all, avoiding conditionals in there seems to be the right way to go about things. By taking a close look at it, though, my eyes opened to the actual depth it has.
Decorators are about adding functionality to an object without actually changing the object or its class.
Not to mention it’s a flexible alternative to inheritance that happens dynamically at runtime.
Using decorators to clean up your views
Let’s start with a simple example. Say you have a Client
model with an email
field. The client can optionally publish their email in their contact info page. How should you display the client’s email? Well, there are a few approaches to this problem. Either you create a method in the model for the view to display:
# app/models/client.rb
class Client < ApplicationRecord
def contact_info
if public_email?
email
else
"This user opted to not publish contact information!"
end
end
end
Or you decide it is the view’s responsibility:
<!-- app/views/show.html.erb -->
<label>Email:</label>
<% if @client.public_email? %>
<p><%= @client.email %></p>
<% else %>
<p>This user opted to not publish contact information!</p>
<% end %>
I guess I don’t have to convince you that neither of these solutions is optimal. First off, your models shouldn’t be concerned with displaying information. It just doesn’t belong in there. And secondly, your views should be dumb and not hold any kind of logic. Their job is to just sit there and look pretty. So here’s the catch: where should this functionality live if not in the model or the view? That’s where a decorator comes into play.
The Draper gem
The Draper Gem is a popular way to implement decorators in Rails, so I’m going to use it in my examples. And here’s something interesting:
“When used with the MVC view layer, the Decorator pattern becomes more specific and is usually called Presenter. All presenters are decorators, but not all decorators are presenters” — Thiago Araújo Silva
However, we’re going to stick with “Decorator” because it’s the convention adopted by the Draper gem.
First off, you should include the Draper gem in your Gemfile
and run bundle install
:
gem 'draper'
After that, you should run rails generate draper:install
to create the ApplicationDecorator
which all decorators will inherit from.
To generate a decorator for the Client
model, run the following command:
$ rails generate decorator Client
If you look into the app/decorators
folder, you’ll see a new client_decorator.rb
file similar to this:
# app/decorators/client_decorator.rb
class ClientDecorator < ApplicationDecorator
# ...
end
So I bet you already guessed the next step — creating the method inside the decorator, similarly to how people would do it in the model:
# app/decorators/client_decorator.rb
class ClientDecorator < ApplicationDecorator
def email
if object.public_email?
object.email
else
"This user opted to not publish contact information!"
end
end
end
At first, there was a fact that I found hard to grasp: at the same time the decorator delegates to the underlying instance, it is able to access the instance itself via the object
method. Therefore, calling object.email
from within the decorator should return the email
of whichever Client you’re decorating, which allows extending the email method.
A decorator augments the original object. It will still respond to any methods the original object responds to, as long as they haven’t been overriden.
To display the email in your views, you can simply call:
<!-- app/views/index.html.erb -->
<span class="email">
<%= @client.email %>
</span>
Well, now it looks a lot better! No more bloating in your models and views. Now let’s learn how to use the decorator. You can initialize it by passing the Client
instance dynamically as a parameter:
# Decorates the first client
decorator = ClientDecorator.new(Client.first)
Which means the object inside the decorator will be that Client.first! Now let’s finish it up by initializing the decorator in the controller’s show action:
# app/controllers/clients_controller.rb
class ClientsController
# GET /clients/1
# GET /clients/1.json
def show
client = Client.find(params[:id])
@client = ClientDecorator.new(client)
end
end
Or if you want a shorter syntax:
# app/controllers/clients_controller.rb
class ClientsController
# GET /clients/1
# GET /clients/1.json
def show
@client = Client.find(params[:id]).decorate
end
end
And that’s it!
Decorators without Draper
If you don’t like Draper for some reason, you can still implement decorators in Ruby with SimpleDelegator
, a jewel from Ruby’s standard library. A simple example would be something like this:
require 'delegate'
class User
def born_on
Date.new(1989, 09, 10)
end
end
class UserDecorator < SimpleDelegator
def birth_year
born_on.year
end
end
decorated_user = UserDecorator.new(User.new)
decorated_user.birth_year #=> 1989
decorated_user.__getobj__ #=> #
All you have to do is make your decorator class extend SimpleDelegator
and initialize it by passing the decorated object. The __getobj__
method is similar to Draper’s object method and allows getting hold of the decorated object.
As the decorator pattern demands, SimpleDelegator forwards unknown calls to the underlying object and also allows overriding its methods.
Decorator as a generic pattern
Decorators are a great place to hold view-related logic, but the rest of your code can still make use of the pattern with great leverage. After all, decorators can be used to augment the functionality of any object.
For example, suppose you have an HTTPClient
class with a single responsibility but you want it to support retrying requests. To not bloat the original class, you can wrap your HTTPClient
in an HTTPClientWithRetry
decorator:
http_client = HTTPClient.new
response = http_client.get(endpoint_url)
# Ooops.. too many requests
response.too_many_requests? #=> true
# Let's decorate the HTTP Client to add retry functionality
http_client = HTTPClientWithRetry.new(http_client, max_retries: 5, wait_time: 1.second)
# Now the HTTP client will retry 5 times with a wait time of
# 1 second until getting a 200 status
response = http_client.get(endpoint_url)
We’ll skip the actual implementation of these classes, but I hope you get an idea of how useful the pattern can be to improve your code when it makes sense to use it!
The Service Object Pattern
This is potentially where people will start getting confused. The motivation behind services in Rails, in my experience, is similar to decorators: you have functionality that you want to give to something, but you don’t think it fits said thing, so you create another thing to do it.
So what’s a service?
A service is a top-level interaction layer responsible for coordinating the work associated with a specific use case or feature.
In other words, services can be understood as the immediate layer that the controller should delegate work to. They also enable the controller to do what it should: focus on handling requests and delivering responses.
So here’s the thing: you usually want your controllers and models to be skinny. You don’t want them to become bloated and thus harder to maintain. And as I said, your views should be dumb, as dumb as they can get. They need to just stand there and look pretty.
And there you have the motivating issue behind services. It might be similar to decorators, but it’s not really the same thing. Services are a whole different beast, and most of the time you’ll end up using both services and decorators in a project. In my personal experience, services are mostly responsible for DRYing up controllers, and decorators for DRYing up views.
An example
Say you have a payment system. It creates orders and calls into an external API to check whether the card the user entered is valid, has enough money, and so on and so forth.
The first solution to this problem could be something like this:
# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
def create
api_response = validate_credit_card(checkout_params)
api_response_body = JSON.parse(api_response_body)
if api_response == 200 && api_response_body['status'] != 'failed'
order_attributes.merge!(order_token: api_response_body['items'][0]['order']['id'])
else
Rails.logger.error api_response_body
order_attributes.merge!(order_status: :refused)
end
order = Order.create(order_attributes)
if order.new_request?
send_alerts(order)
redirect_to action: 'show', id: order.id
else
render 'unauthorized'
end
end
def validate_credit_card
# ...
end
end
The above code:
- Calls into an API,
- Parses the response,
- Updates order attributes based on the API response,
- Sends alerts to the customer and other interested parties,
- Redirects the customer on success, or renders an unauthorized page on failure.
Such code makes my spider sense tingle. Steps 1 to 4 feel out of place, and should not be the controller’s responsibility. Step 5, on the other hand, looks very much like something a controller should do. Is that the case for a decorator? No, because a decorator would hinder the ability to use the code outside of the context of a controller. A ControllerDecorator
is definitely out of question.
What to do then? You clearly need a stage where the interaction between these four different pieces — an API caller, a parser, an order creator, and a messenger — will happen, and it’s clearly not the controller.
To solve this problem, you can create a service class. Something like this:
# app/services/create_order_service.rb
class CreateOrderService
def call(checkout_params, order_attributes)
api_response = validate_credit_card(checkout_params)
api_response_body = JSON.parse(api_response_body)
if api_response == 200 && api_response_body['status'] != 'failed'
order_attributes.merge!(order_token: api_response_body['items'][0]['order']['id'])
else
Rails.logger.error api_response_body
order_attributes.merge!(order_status: :refused)
end
Order.create(order_attributes)
end
def validate_credit_card
# ...
end
end
The nice thing about a service class is that you can test it without spinning up a controller, and also reuse it in other places.
And now your controller can simply call the service:
# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
def create
order = CreateOrderService.new.call(checkout_params, order_params)
if order.new_request?
redirect_to action: 'show', id: order.id
else
render 'unauthorized'
end
end
end
Which just straight up just looks a lot cleaner and improves readability and maintainability! It has the same functionality as before but doesn’t bloat your controller with code not relevant to it.
Refactoring
But hold up, we’re not done yet. All we’ve done was relocate code from controller to service, but we can still do better and improve the code:
# app/controllers/create_order_service.rb
class CreateOrderService
Result = Struct.new(:order) do
def success?
order.new_request?
end
end
def call(checkout_params, order_params)
order_token = ValidateCreditCard.call(checkout_params)
order_params = finalize_order_params(order_params, order_token)
order = Order.create(order_params)
result = Result.new(order)
send_alerts(order) if result.success?
result
end
private
def finalize_order_params(order_params, order_token)
if order_token
order_attributes.merge(order_token: order_token)
else
order_attributes.merge(order_status: :refused)
end
end
def send_alerts(order)
# ...
end
end
Great! There are four things worth noting in this new version:
- We’ve created private methods to better explain what’s going on,
- We’ve extracted the code to validate credit cards out to a separate class. A service is not an excuse to not think about your domain model.
- We are no longer changing
order_params
in place withmerge!
. Instead, we are usingmerge
to avoid side-effects propagating down to the caller. - We are returning a result object. Result objects are great to enforce a more functional style and standardize the return value of services objects, which shouldn’t return Active Record objects or other random objects.
And here’s the final version of the controller:
# app/controllers/orders_controller.rb
class OrdersController
def create
result = CreateOrderService.new.call(checkout_params, order_params)
if result.success?
redirect_to action: 'show', id: result.order.id
else
render 'unauthorized'
end
end
end
The concept of “success” is encapsulated within the result object, and the controller is therefore exempted from knowing about these details.
Wrap up
If you’ve read it all the way down here, I hope you’ll finish your day with a bit more knowledge then you started with, and even more so if you’re a rookie like me! So go forth and use your newfound knowledge on a couple of design patterns to wreak havoc and write some really good code.
Thanks to Talysson de Oliveira, Jonatas Rancan, Luan Gonçalves, and Thiago Araújo Silva.
We want to work with you. Check out our "What We Do" section!