Dryer Services
A gem providing base classes for composable service object that leverages the dry-monads gem for error handling.
Installation
add the following to you gemfile
gem "dryer_services"
Usage
This gem provides two base classes, SimpleService
and ResultService
.
Both classes provide a single class method call
, and require the inheriting class to
define the instance methods initialize
and call
SimpleService
is meant to be used for operations that have no failure modes,
while ResultsService
is meant to be used for operations that may fail.
SimpleService Example
class Add < Dryer::Services::SimpleService
def initialize(a, b)
@a = a
@b = b
end
def call
a + b
end
private
attr_reader :a, :b
end
Add.call(1,2) # returns 3
ResultService Examples
ResultService wraps the value returned from call
in a Dry::Monad::Result
.
There are several cases
- If the return value is an Error, it will return a Failure
- If the return value is not an Error, and is not a Result, it will wrap the
return value in a Result
- If the return value is a list of Results eg [Success(1), Success(2)] it
will condense those results into a single Result eg Success([1,2]). If any
of the results in the list are Failures eg [Success(1), Failure(2)], It
will return the first failure encountered eg Failure(2). Changing a list
of Monads (Results) into a Monad containing a list of values, is called
'traversing'
- If the return value is already a
Dry::Monad::Result
, it will not wrap the result
Wrapping an error in a Failure
class Divide < Dryer::Services::ResultService
def initialize(a, b)
@a = a
@b = b
end
def call
if b == 0
StandardError.new("Can not divide by zero")
else
a/b
end
end
private
attr_reader :a, :b
end
Divide.call(4,2) # returns Dry::Monads::Success(2)
Divide.call(4,0) # returns Dry::Monads::Failure("Can not divide by zero")
Traversing a list of results
class DivideMany < Dryer::Services::ResultService
def initialize(a, b)
@a = a
@b = b
end
def call
# each call to Divide returns a Result
# so we are returning a list of Results
a.map { |x| Divide.call(x,b) }
end
private
attr_reader :a, :b
end
DivideMany.call([2,4,6],2) # returns Dry::Monads::Success([1,2,3])
DivideMany.call([2,4,6],0) # returns Dry::Monads::Failure("Can not divide by zero")
Advantages
Using the Service pattern can help to make code more modular, and make it easier to separate data modeling from transformations.
Development
This gem is set up to be developed using Nix and
ruby_gem_dev_shell
Once you have nix installed you can run make env
to enter the development
environment and then make
to see the list of available commands
Contributing
Please create a github issue to report any problems using the Gem. Thanks for your help in making testing easier for everyone!
Versioning
Dryer Services follows Semantic Versioning 2.0 as defined at https://semver.org.
License
This code is free to use under the terms of the MIT license.