SimpleService
SimpleService facilitates the creation of Ruby service objects into highly discreet, reusable, and composable units of business logic. The core concept of SimpleService is the definition of "Command" objects/methods. Commands are very small classes or methods that perform exactly one task. When properly designed, these command objects can be composited together or even nested to create complex flows.
Installation
Add this line to your application's Gemfile:
gem 'simple_service'
And then execute:
$ bundle
Or install it yourself as:
$ gem install simple_service
Setup and Basic Usage:
- load the gem
- include the SimpleService module into your service object class
- define one or more comamnds that it will perform, must accept either keyword arguments or a hash argument
- call
#successor#failurewith any values you wish to pass along to the next command (or wish to return if it is the last command)
require 'rubygems'
require 'simple_service'
class DoStuff
include SimpleService
commands :do_something_important, :do_another_important_thing
def do_something_important(name:)
= "hey #{name}"
success(message: )
end
def do_another_important_thing(message:)
= "#{}, we are doing something important!"
success(the_final_result: )
end
end
result = DoStuff.call(name: 'Alice')
result.success? #=> true
result.value #=> {:the_final_result=>"hey Alice, we are doing something important!"}
A failure:
require 'rubygems'
require 'simple_service'
class DoFailingStuff
include SimpleService
commands :fail_at_something
def fail_at_something(name:)
= "hey #{name}, things went wrong."
failure(message: )
end
end
result = DoStuff.call(name: 'Bob')
result.success? #=> false
result.failure? #=> true
result.value #=> {:message=>"hey Bob, things went wrong."}
You can also use ClassNames as commands and to organize them into other files:
require 'rubygems'
require 'simple_service'
class CommandOne
include SimpleService
command :add_stuff
def add_stuff(one:, two:)
success(three: one + two)
end
end
class CommandTwo
include SimpleService
command :add_more_stuff
def add_more_stuff(three:)
binding.pry
success(seven: three + 4)
end
end
class DoNestedStuff
include SimpleService
commands CommandOne, CommandTwo
end
result = DoNestedStuff.call(one: 1, two: 2)
result.success? #=> true
result.value #=> {:seven=>7}
If you would like your service to process an enumerable you can override #call
on your service object. Invoking #super in your definition and passing along
the appropriate arguments will allow your command chain to proceed as normal, but
called multiple times via a loop. The Result object returned from each call to #super
can be passed in as an argument to the next iteration or you can collect the result objects
yourself and then do any post processing required.
require 'rubygems'
require 'simple_service'
class LoopingService
include SimpleService
commands :add_one
def call(kwargs)
count = kwargs
3.times do
count = super(count)
end
count
end
def add_one(count:)
success(count: count + 1)
end
end
If you are using this with a Rails app, placing top level services in
app/services/ and all nested commands in app/services/commands/ is
recommended. Even if not using rails, a similar structure also works well.
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create new Pull Request