This is the third in a series of three posts that will demonstrate implementation of three common Rails design patterns: the adapter pattern; service objects; the decorator pattern
You can find the code for these posts here You can find a demo of the app, deployed to Heroku, here.
In the previous two posts in this series, we made our controllers clean and DRY by implementing the adapter patter, along with a series of service objects.
We built adapters to wrap our interactions with the GitHub and Twilio APIs and we built services to carry out the tasks of creating new repos, updating issues and sending email and text notifications.
In this final part of our refactoring series, we'll clean up our views and models by implementing the decorator pattern.
What is the Decorator Pattern?
The decorator pattern allows us to add behavior to an instance without affecting the behavior of all the instances of that class. We can “decorate” or add behavior to, a model instance before passing it to a view. Or, we can decorate an instance within a view.
By extracting complex presentation logic out of our views and models, and into decorator classes, we can simplify views (which should be stupid) and models (which shouldn't know about presentation), and decorate instances as needed.
Let's get started!
Implementing the Decorator Pattern
Let's take a look at one of our view-related code smells––the code we use to display a repository's name:
# app/views/repositories/_repo.html.erb <%= @repository.name.gsub("-", " ").titleize %>
The same code is used on the issue's show page, when rendering the name of the repo to which it belongs:
# app/views/issues/show.html.erb <%= @issue.repository.name.gsub("-", " ").titleize %>
Not only is this code repetitious, it's also using an awful lot of Ruby to manipulate a string into looking pretty for us. This is not the responsibility of the view. The view just needs to sit there and look pretty, like a pre-Suffragism Victorian lady.
Heaven forbid I handle my own view logic. I prefer to just sit here and mind my cats. - pintrest.com
Let's implement a decorator to handle the presentation of a repository's name.
- Define a subdirectory,
- Define a file
Here we'll define our decorator with a method,
#display_name, to encapsulate the repo name display logic.
# app/decorators/repository_decorator.rb class RepositoryDecorator < SimpleDelegator def display_name name.gsub("-", " ").titleize end end
Note that our decorator inherits from
What is SimpleDelegator?
SimpleDelegator is a native Ruby class that
provides the means to delegate all supported method calls to the object passed into the constructor. –– Ruby Docs
In other words, we can instantiate our
RepositoryDelegator with an argument of a
Repository instance, and, because it inherits from
SimpleDelegator, we can treat that new delegator instance just like an instance of
Repository, with the added functionality of our delegator.
Let's use it!
# app/views/repositories/_repo.html.erb <%= RepositoryDecorator.new(@repository).display_name %>
# app/views/issues/show.html.erb <%= RepositoryDecorator.new(@issue.repository).display_name %>
Let's take a look at another code smell that could benefit from some decorator refactoring, this time in the model.
Currently, we have a model method on our
User model that deals with presentation logic.
# app/models/user.rb def display_phone_number phone_number && !phone_number.empty? ? phone_number : " <i>add your phone number to receive text message updates</i>".html_safe end
This method is called in the view like so:
# app/views/users/show.html.erb ... <p>phone number: <%= @user.display_phone_number %>
It is absolutely a violation of the separation of concerns and the single responsibility principle to have the
User model be responsible for this presentation logic.
Let's build a decorator!
- Generate a file,
Here, we'll define our decorate to have a method,
# app/decorators/user_decorator.rb class UserDecorator < SimpleDelegator def display_phone_number phone_number && !phone_number.empty? ? phone_number : " add your phone number to receive text message updates".html_safe end end
Now, we can use it in our views like so:
# app/views/users/show.html.erb ... <p>phone number: <%= UserDecorator.new(@user).display_phone_number %>
That's it for our Rails Refactoring series. Keep in mind that these posts are not meant to say "this is how you refactor", but rather "here are some techniques you can apply when refactoring".
You can find Part I here; and Part II here. You can find the final refactored code for this project here, and the pre-refactor version here.
Lastly, here are some resources:
- Uncle Bob, Does Organization Matter?
- Uncle Bob, Code Hoarders
- Sandi Metz, Practical Object-Oriented Design in Ruby