Project

statefully

0.0
No commit activity in last 3 years
No release in over 3 years
Immutable state with helpers to build awesome things
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 1.14.6, ~> 1.14
>= 0.1.10, ~> 0.1
>= 0.10.4, ~> 0.10
~> 12.0
>= 4.6.2, ~> 4.6
~> 3.6
~> 0.49
>= 1.15.1, ~> 1.15
>= 0.14.1, ~> 0.14
>= 0.9.9, ~> 0.9
>= 0.9.9, ~> 0.9
 Project Readme

Statefully: immutable state library for Ruby

Gem Version codecov Codefresh build status

Statefully is an immutable state library to serve as a building block for awesome, composable APIs. Its core concept is State, which can be either a Success, a Failure or Finished (successful but terminal). Code speaks louder than words, so here's a few examples:

> state = Statefully::State.create
=> #<Statefully::State::Success>
> state = state.succeed(key: 'val')
=> #<Statefully::State::Success key="val">
> state = state.fail('Oh no')
=> #<Statefully::State::Failure key="val", error="\"Oh no\"">
> state.resolve
RuntimeError: Oh no
        [STACK TRACE]
> state.previous.resolve
=> #<Statefully::State::Success key="val">
> state = state.previous.finish
=> #<Statefully::State::Finished key="val">
state.succeed(new_key: 'new_val')
NoMethodError: undefined method `succeed' for
  #<Statefully::State::Finished key="val">
        [STACK TRACE]

The core API is really simple - State::Success has three methods, each of which produces an instance of State: succeed will produce another State::Success, finish will produce a State::Finished and fail will produce a State::Failure. Each State can access its predecessor by calling the previous method. The rest of the State API are just convenience wrappers, except perhaps for the diff method, described below.

When State is first created, it adds a correlation_id field, which is a dynamically generated UUID. It is useful to track execution in environments where you can't query the Statefully::State for its predecessors - like logs. If you want to provide a custom value - for example from your web framework, just pass it to the Statefully::State.create call.

Diffs

Since State always knows about its predecessor, we can use the diff method to compare the two:

> state = Statefully::State.create
=> #<Statefully::State::Success>
> state = state.succeed(key: 'val')
=> #<Statefully::State::Success key="val">
> state.diff
=> #<Statefully::Diff::Changed added={key: "val"}>
> state = state.update(key: 'another')
NoMethodError: undefined method `update' for
  #<Statefully::State::Success key="val">
        [STACK TRACE]
> state = state.succeed(key: 'another')
=> #<Statefully::State::Success key="another">
> state.diff
=> #<Statefully::Diff::Changed
     changed={key: #<Statefully::Change current="another", previous="val">}>

In fact, because each State knows about its predecessor, we can diff recursively. A convenience method called history does just that, with newest changes first:

> state.history
=> [#<Statefully::Diff::Changed
      changed={key: #<Statefully::Change current="another", previous="val">}>,
    #<Statefully::Diff::Changed added={key: "val"}>,
    #<Statefully::Diff::Created>]

Diff API is very simple, too. You can compare arbitrary States using Statefully::Diff.create, but be aware that we assume state keys are never removed - they may be added or their values may change. So, it only makes sense to compare States within the scope of the same history.