Skip to content

lucianghinda/simple-result

Repository files navigation

SimpleResult

A simple, idiomatic Ruby implementation of a response monad (Success or Failure) for handling success and failure states inspired by https://github.com/maxveldink/sorbet-result.

This is a very simple implementation kept on purpose to be less than 100 LOC (currently at 60) and with no other dependencies than zeitwerk.

Installation

Add this line to your application's Gemfile:

gem 'simple_result'

Usage

The recommended usage is to use the Success and Failure helpers to create responses. You can also use the scoped classes 'SimpleResult::Success' and 'SimpleResult::Failure' if you want.

Using Helpers

require 'simple_result'

class MyObject
  include SimpleResult::Helpers

  def initialize(value)
    @value = value
  end

  def double
    Success(@value * 2)
  end
end

object = MyObject.new(5)
result = object.double

result.success? # => true
result.payload  # => 10

result = object.double.and_then { |value| value + 1 }

result.success? # => true
result.payload  # => 11

Without Helpers

require 'simple_result'

class MyObject
  def initialize(value)
    @value = value
  end

  def double
    SimpleResult::Success.new(@value * 2)
  end
end

object = MyObject.new(5)
result = object.double

result.success? # => true
result.payload  # => 10

result = object.double.and_then { |value| value + 1 }

result.success? # => true
result.payload  # => 11

Basic Usage

require 'simple_result'
include SimpleResult::Helpers

success = Success("Hello, World!")
success.success? # => true
success.payload  # => "Hello, World!"

failure = Failure("Something went wrong")
failure.failure? # => true
failure.error    # => "Something went wrong"

# Blank responses
Success() # => Success with nil payload
Failure() # => Failure with nil error

Chaining Operations

Success("hello")
  .and_then { |value| value.upcase }
  .and_then { |value| "#{value}!" }
# => "HELLO!"

# Failure short-circuiting
Failure("error")
  .and_then { |value| value.upcase }
  .and_then { |value| "#{value}!" }
# => Failure("error")

A more common case of chaining operations might look like this

def validate(value)
  # validation
  Success(value)
end

def transform(value)
  transformed_value = value.upcase

  Success(transformed_value)
end

def present(value)
  presenter = "#{value}!!"

  Success(presenter)
end

validate("hello")
  .and_then { |value| transform(value) }
  .and_then { |value| present(value) }
# => "HELLO!"

# Or since Ruby 3.4 you can also use the implicit `it` block parameter
validate("hello")
  .and_then { transform(it) }
  .and_then { present(it) }

# or if you prefer to write without paranthesis in a more DSL like style:
validate("hello")
  .and_then { transform it }
  .and_then { present it }

Error Handling

# Handle errors only when they occur
Success("data").on_error { |error| puts "Error: #{error}" }
# => Success("data") - block not called

Failure("oops").on_error { |error| puts "Error: #{error}" }
# => Prints "Error: oops", returns Failure("oops")

Fallback Values

The fallback will be called only when the result is nil or an error. Please be carefull when using blank Success and Failure values with payload_or_fallback because Success().payload_or_fallback will always return the fallback value.

# Use payload or fallback
Success("value").payload_or_fallback { "default" }
# => "value"

Success(nil).payload_or_fallback { "default" }
# => "default"

Failure("error").payload_or_fallback { "default" }
# => "default"

Development

Run tests:

ruby test/test_simple_result.rb

Run RuboCop:

rubocop

Start an interactive console:

bin/console

License

The gem is available as open source under the MIT License.

About

A simple, idiomatic Ruby implementation of a response monad (`Success` or `Failure`) for handling success and failure states

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages