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:
- Review different practices
- Showcase some gems
- List things I would like to see after 10 years of practice
- 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
-
Anatomy of a Rails Service Object Dave Copeland ↩