Many years ago, service objects in Rails became popular. And if they are still used today, there are very good reasons for that. In this series of articles, I will give you a retrospective on one of the least official but most useful layers of Ruby on Rails.

Soon, this kind of code will have no secrets for you.

result = MyService.call(params)

Some Links

If you do some research, you will see that more than 10 years ago, people were already talking about it. You can see some blog posts from June 02, 2015 1 or some StackOverflow questions asked on Jul 16, 2014 2. And people are still talking about it today.

There is still no official convention for this layer, and many teams and independent developers have created their own style and sometimes their own gems.

What is it?

Service Objects in Rails are a way to extract business logic from a controller or model to improve code maintainability. Instead of burdening models or controllers with additional responsibilities, a Service Object encapsulates a single, reusable action.

Utility

Separation of Responsibilities

Avoids Fat Models and Fat Controllers. Each class has a single responsibility.

Reusability

A service can be used in multiple controllers or jobs without duplication.

Readability and Testability

A well-written service is easier to understand than a large model. Unit tests become simpler.

Easier Extension

Adding a new feature is cleaner and more structured.

Facilitates the Introduction of Functional Programming

A service can return result objects (Success/Failure) for better error handling.

Avoids Issues Related to Callbacks

Callbacks in models have many advantages, but they also have some long-term drawbacks.

Purpose of this Article Series

In this series of articles, I will:

  1. Review different practices
  2. Showcase some gems
  3. List things I would like to see after 10 years of practice
  4. Propose the beginning of a gem that could synthesize the best of everything

Example

You didn’t come here for nothing! Here is a first example:

# Example of a service
class MyService
  def self.call(*args)
    new(*args).call
  end

  def initialize(params)
    @params = params
  end

  def call
    # Implementation of business logic
  end
end