Service objects are Plain Old Ruby Objects (POROs) which encapsulate a whole business process/user interaction.
Our services are located in app/services
with the corresponding specs in spec/services
. Their main interface is a class level method named call
, as in the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13
class ImportUsers def self.call(arg1) new(arg1).call end def initialize(arg1) @arg1 = arg1 end def call # import code goes here end end
To distinguish services from models we often give them verb names vs noun names, e.g. ImportUsers
instead of UserImporter
.
To make our services more consistent we use a custom Rails generator. Some usage examples:
Generate a non-namespaced service without arguments
$ rails generate service DoTheThing
1 2 3 4 5 6 7 8 9
# app/services/do_the_thing.rb class DoTheThing def self.call new.call end def call end end
1 2 3 4 5 6
# 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
Generate a non-namespaced service with arguments:
$ rails generate service DoTheThing arg1 arg2
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# app/services/do_the_thing.rb class DoTheThing def self.call(arg1, arg2) new(arg1, arg2).call end def initialize(arg1, arg2) @arg1 = arg1 @arg2 = arg2 end def call end end
The generated spec is the same as above.
Generate a namespaced service with arguments
$ rails generate service things/dothem arg1 arg2
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# 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
1 2 3 4 5 6
# 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