Rails Refactoring Part III: The Decorator Pattern

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

View Logic

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 %>  

Code Smells

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.

Fix It

  • Define a subdirectory, app/decorators
  • Define a file app/decorators/repository_decorator.rb

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 SimpleDelegator.

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.

Model Methods

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 %>  

Code Smell

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.

Fix It

Let's build a decorator!

  • Generate a file, app/decorators/user_decorator.rb

Here, we'll define our decorate to have a method, #display_phone_number.

# 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 %>  

Much cleaner!

Conclusion

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:

Happy coding!

subscribe and never miss a post!

Blog Logo

Sophie DeBenedetto

comments powered by Disqus
comments powered by Disqus