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
# 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
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
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
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.
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']['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']['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.
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 with `merge!`. Instead, we are using `merge` 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.
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 are hiring new talents. Do you want to work with us? email@example.com