From personal experience there are a couple of strong use cases for service objects.
Firstly if you follow Rails' "fat models thin controllers" methodology religiously you can easily end up with monolithic models, which is where ActiveSupport "concerns" come in. Except that we never really got on with them for one reason or another, mostly because it doesn't solve the monolithic models problem so much as sweep it under a different carpet.
Secondly if you have specific areas of interaction between different models, it's much easier to maintain (and clearer to read) service objects than it is to push these interactions into the models (making them more monolithic), and more reusable than this functionality living in the controllers (where it shouldn't be anyway).
Service objects are like a middle ground between the model and the controllers where it is easier to talk about multiple models and the interactions between them.
For example lets say you have User, Timesheet, and Invoice models and you need to implement a routine that generates a set of invoices for a given user for all of their outstanding timesheets, where each invoice corresponds to a given month of submitted timesheets.
If you were to implement this in the model, the first question is where should it live? Semantically this code seems like it belongs in the Invoice model since we're talking about invoices, but what if timesheets aren't the only way to generate invoices? Are we going to have to bloat the Invoice model every time we need a new way to generate one? We could use ActiveSupport concerns and abstract this code away a bit to keep app/models/invoice.rb nice and tidy - but we're just burying business logic deeper inside our codebase. We could try adding this code to Timesheet or User instead - but this is even less clear (developer thinks "where do I find the invoice generation routines, surely in the Invoice model, right?").
Instead, using a service object negates all of these problems. It's clear where the business logic is (it's in app/services/!) and a service object is free to orchestrate any combination of models it needs without worrying about logical boundaries between them.
Simple example:
# Does what it says on the tin; generates invoices for a user's timesheets
class TimesheetsInvoicesGenerator
def generate_for_user(user)
user.timesheets.not_invoiced.group_by(&:month).map |month, timesheets|
Invoice.generate_for(timesheets)
end
end
end
A few simple rules I follow when using service objects:
It took a bit of fiddling around to find a medium I was happy with, but ultimately I ended up with:
And I don't have to go gallivanting around my source code to remember where I put things 12 months ago.
Hope that helps. I'm a little rusty as I have had my head deep in Python, Saltstack and Docker for the past 6 months and haven't really touched our Rails app much.