Skip to content
Immutable state with helpers to build awesome things
Ruby Shell
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.

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 ='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.


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"}>,

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.

You can’t perform that action at this time.