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.

Basic Usage

require 'simple_result'

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.