I have read a lot of Rails articles and most of them always tell you to keep your Rails controllers skinny. It sounds so easy but it’s really not. As the cliche goes, “it is easier said than done.” In real life, requirements get complicated. Controllers and models get longer and longer and you need to do something about it. The itch to refactor is for real!
ENTER SERVICE OBJECTS.
What are service objects?
My understanding is that, service objects are actions or logic operations that don’t belong to a single or specific object in your Rails app. If you can’t pinpoint immediately where a certain operation must live, it’s probably a service. Services have their own home and are in a different layer in the app– the service layer.
There are many kinds of service object. Based on what I read, these are the 3 mostly used ones:
- Application – used in the application for whatever action / logic needs to be re-usable
- Infrastructure – used when action/logic involves external systems/APIs
- Domain – simply, “multi-model” stuff. For example, you have to update model B when model A is created and then insert a new entry to model C.
For #3, I used to implement those kinds through hooks like
afer_create, but it breaks SRP! It’s not the job of model A to update model B and insert to model C. (Yes. Things can get complicated like this!)
Below is a sample of an application service object that checks if certain limits have been reached. It can be used by the API, model, controller, or whichever needs checking. (For this to be service-object-worthy, I think it should be complicated stuff. Do not do this if you’re only comparing basic things like if count is greater than 1, or something.)
[pastacode lang=”ruby” path_id=”7d6829c373a3aa91002a” file=”” highlight=”” lines=”” provider=”gist”/]
The one below is a domain service object. (I’d like to think so!) It can be re-usable in the controllers that need it. It updates/inserts to several other models not related to the current one.
[pastacode lang=”ruby” path_id=”02b51d512da925c2b7aa” file=”” highlight=”” lines=”” provider=”gist”/]
Service objects keep my controllers DRY. My service object adventures are not yet fully “pro” but as I go along and build even more complex stuff, they’ll surely come in handy.