Skip to main content

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 of Reaction::.
  • To distinguish services from models we often follow the VerbNoun naming convention for services, like HomePage::FetchArticles instead of HomePage::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