0.0
A long-lived project that still receives updates
An extension of the Dry family of gems (dry-rb.org). This gem provides a base service class that leverages the dry-monads gem to build composable service objects.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.8
~> 3.10

Runtime

 Project Readme

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.