Computer >> Computer tutorials >  >> Programming >> ruby

Rails Design Patterns: Presenter & Service Objects

Why do we need design patterns?

The problem is that Rails architecture, Model-View-Controller, gives you a basic structure to put your code in.

But this isn’t enough.

Your views grow large & full of logic when their goal is to present information.

Your controllers hold details beyond what’s necessary for the controller to do its essential work.

What’s the solution?

We have created two solutions to solve these problems, in the form of design patterns.

  • The presenter pattern
  • The service object pattern

Not everyone agrees on how to exactly implement them, but I’ll give you the version that works for me.

Let’s explore these patterns!

How to Use Presenters in Rails

Views are for presentation, that means HTML, CSS, & ERB (Embedded Ruby).

There should be no ActiveRecord queries in views.

And most logic should be left out if you want your views to be as clean & easy to work with as possible.

By “logic” I mean making decisions with if statements & ternary operators

The question now is…

How?

Your first tool to handle logic in views is to use helpers.

Helpers are great whenever you have a global formatting method that you use in many views.

For example:

Rendering Markdown, showing dates in a specific format, removing specific words from text, etc.

Like this:

module DateHelper
  def date_as_month_and_year(date)
    date.strftime("%B %Y")
  end
end

You can save this code under the app/helpers folder & the date_helper.rb file.

Here’s a tip:

Always pass input into helper methods via arguments, never rely on instance variables.

This will save you a lot of trouble.

Helper methods have limitations, especially if you use them for every formatting need in your views.

They tend to build up & lack any kind of organization.

Solution coming up!

Replace Complex Conditionals & Formatting Methods With Presenter Object

Let’s say you have a view like this one:

<p>
  Post title: <%= post.title.gsub("forbidden word", "") %>

  <%= link_to "Read post", post, class: "w-75 p-3 text-#{post.draft? ? "orange" : "green"} border-#{post.draft? ? "orange" : "green"}" %>
</p>

Pretty short view, right?

But it feels very complex with these ternary operators & the duplicated code.

Not good!

Let’s create a presenter class to solve this.

Here’s how:

class PostPresenter
  def initialize(post)
    @post = post
  end

  def title_without_forbidden_words
    @post.title.gsub("forbidden word", "")
  end

  def css_color
    @post.draft? ? "orange" : "green"
  end
end

Save this under app/presenters/post_presenter.rb, create the presenters folder if you don’t have it.

Now you can change the view.

Like this:

<% presenter = PostPresenter.new(post) %>

<p>
  Post title: <%= presenter.title_without_forbidden_words %>

  <%= link_to "Read post", post, class: "w-75 p-3 text-#{presenter.css_color} border-#{presenter.css_color}" %>
</p>

There you go!

  • We removed all the logic from the view
  • We added meaningful names for the formatting & decision-making operations
  • We can reuse this class in other views, without duplicating code

That’s how you use presenters in Rails 🙂

How to Use Service Objects

Your controllers should only tell others what to do, they shouldn’t have any knowledge about how to send a Tweet, charge a customer or generate PDF files.

These operations should be delegated to a service object.

A service object, as I define it, is a Ruby module which encapsulates the logic for completing an action.

Example:

module TwitterService
  def self.send_welcome_message(twitter_handle)
    client.update("@#{twitter_handle} welcome to 'Oranges & Apples', we hope you enjoy our juicy fruit!")
  end

  def self.client
    @client ||= Twitter::REST::Client.new do |config|
      config.consumer_key        = "..."
      config.consumer_secret     = "..."
      config.access_token        = "..."
      config.access_token_secret = "..."
    end
  end
end

The convention is to save this under an app/services folder, and a file like twitter_service.rb.

How do you use this?

Because Rails autoloads everything from app/, this code will be available in your controllers.

Example:

class UsersController
  def create
    # ...
    TwitterService.send_welcome_message(user.twitter_handle)
  end
end

That’s the service object pattern in action.

Summary

You have learned two helpful Rails patterns that will help you improve your project’s code quality when used wisely!

It’s your turn now to apply them 🙂

Thanks for reading.