RubyGuides
Share this post!

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.

Leave a Comment:

12 comments
Promise Preston says 3 weeks ago

Thank you for this tutorial. I have been finding it difficult to understand these concepts. I have even read tons of tutorials including Trailblazer framework, but your explanation seems to be very explanatory. Do you have other resources to recommend so that I can fully grasp this service-oriented architecture in rails?

Reply
    Jesus Castello says 3 weeks ago

    Thanks for your comment ๐Ÿ™‚

    I haven’t done anything with SOA so I can’t recommend any resources for that. Good luck with your search!

    Reply
Hatem Khattab says 3 weeks ago

Thanks for your effort, very useful article, keep going

Reply
    Jesus Castello says 3 weeks ago

    Thank you ๐Ÿ™‚

    Reply
mpanasiewicz says 3 weeks ago

Hello Jesus,

Thank you for nice article. I’m surprised that you use modules to create service objects.

I’ve learned that it should be a class. Another thing is that service objects call standardized methods like “call”, “execute”, “run”.(depends of project or organization standard). In my opinion service object should look something like this:
https://gist.github.com/mPanasiewicz/6c929ae289836540508d571e5d9b2892

In my opinion Service object should have single responsibility.

Reply
    Jesus Castello says 3 weeks ago

    Hi,

    Like I mention in the introduction, there are different ways to implement this, I’m not a big fan of the call / run method pattern because it doesn’t help add any meaning to what you’re doing & it feels very mechanical to me.

    But feel free to use whatever works for you, I’m just proposing one way to do it ๐Ÿ™‚

    Thanks for your comment.

    Reply
David says 3 weeks ago

Hi Jesus,

Thanks for the tutorial. Two things I’d like to share;
1) You can use module_function to create module functions for the named methods. That way you don’t need to call self

module TwitterService
  module_function

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

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

2) There’s a great talk on Service Objects here: https://www.youtube.com/watch?v=dSiE9N_f0h0
I’ve been using the Interactor gem mentioned in the talk on a project and it’s great.

Reply
    Jesus Castello says 3 weeks ago

    Hi David!

    Thanks for sharing that talk & about module_function, I also like extend self to get the same result ๐Ÿ™‚

    Reply
Mike says 3 weeks ago

Thanks for posting this, a nice solid read. One question, what are your thoughts on creating a wrapper for the Twitter Client, rather than directly accessing it in this service?

Reply
    Jesus Castello says 3 weeks ago

    Can you share a code example with a gist?

    Reply
Jรณni Batista says 3 weeks ago

Hi Jesus, I’m learning a lot from your short pots/videos! Gracias ๐Ÿ˜€

About the presenter pattern, I don’t like two things:
* Another object per each model – I love rails but sometimes it’s frustrating to have too many layers (objects, files and, folders) to make a simple view.
* Hidden object calls inside an abstract variable called “presenter” for all views (harder reading and refactoring)

Since we already have Helpers, why should we use them to do the presenter’s job?

Keep posting great articles! =b

Reply
    Jesus Castello says 3 weeks ago

    Hi Jรณni,
    thanks for your comment!

    1. You don’t need to create a presenter for every single model or view, this is only for when complexity starts building up, you get a feel for it.
    2. If you’re familiar with the pattern it becomes less abstract, but I’m open to other ideas for the naming.

    What I like about presenters is that it makes the presentation layer feel a little bit more object-oriented, helpers alone don’t do that.

    Reply
Add Your Reply