Service Objects
Service objects are Plain Old Ruby Objects (POROs) which encapsulate a whole business process/user interaction. We rely on these objects in our codebase to remove complexity from the Models and Controller.
Our services are located in app/services
with the corresponding specs in
spec/services
.
Naming Conventions for Service Objects​
- We prefer to namespace our Service Objects especially when we potentially see the addition of more services under that namespace. This is adviseable rather than cluttering the root folder.
- We namespace services in a plural form to avoid running into problems with Rails naming conventions. For example
Reactions::
instead ofReaction::
. - To distinguish services from models we often follow the VerbNoun naming convention for services, like
HomePage::FetchArticles
instead ofHomePage::ArticleRetrieval
. However, sometimes the naming of the service may not fit within this convention and if you can rationalize about why you should use a different name then we will leave it to your discretion. - We encourage you to keep the namespaces to be the same across services, workers etc.
.call Pattern​
We use the .call
pattern when creating services. This makes it clear and consistent to identify where our primary logic for the service lies.
For example: We'd prefer result = ProfileFields::Add.call
vs result = ProfileFields::Add.result
.
However, as with the naming conventions if you can rationalize about why you should use a custom method name then we will leave it to your discretion.
Skeleton of a Service Object
Most Services Objects will contain the following skeleton:
class ImportUsers
def self.call(arg1)
new(arg1).call
end
def initialize(arg1)
@arg1 = arg1
end
def call
# import code goes here
end
end
Generating Service Objects​
To make our services more consistent we use a custom Rails generator. Some usage examples:
Generate a non-namespaced service​
$ rails generate service DoTheThing
# app/services/do_the_thing.rb
class DoTheThing
def self.call
new.call
end
def call
end
end
# spec/services/do_the_thing_spec.rb
require "rails_helper"
RSpec.describe DoTheThing, type: :service do
pending "add some examples to (or delete) #{__FILE__}"
end
If there are arguments for your service you can generate it as follows:
$ rails generate service DoTheThing arg1 arg2
Generate a namespaced service​
$ rails generate service things/dothem arg1 arg2
# app/services/things/do_them.rb
class Things::DoThem
def self.call(arg1, arg2)
new(arg1, arg2).call
end
def initialize(arg1, arg2)
@arg1 = arg1
@arg2 = arg2
end
def call
end
end
# spec/services/things/do_them_spec.rb
require "rails_helper"
RSpec.describe Things::DoThem, type: :service do
pending "add some examples to (or delete) #{__FILE__}"
end