class Statefully::State

{State} is an immutable collection of fields with some convenience methods. @abstract

Attributes

_members[R]

State fields

@return [Hash] @api private

previous[R]

Return the previous {State}

@return [State] @api public @example

Statefully::State.create.previous
=> #<Statefully::State::None>

Statefully::State.create.succeed.previous
=> #<Statefully::State::Success>

Public Class Methods

create(**values) click to toggle source

Create an instance of {State} object

This is meant as the only valid way of creating {State} objects.

@param values [Hash<Symbol, Object>] keyword arguments

@return [State::Success] new successful State @api public @example

Statefully::State.create(key: 'val')
=> #<Statefully::State::Success key="val">
# File lib/statefully/state.rb, line 69
def self.create(**values)
  base = { correlation_id: SecureRandom.uuid }
  Success.send(:new, base.merge(values), previous: None.instance).freeze
end

Private Class Methods

new(values, previous:) click to toggle source

Constructor for the {State} object

@param values [Hash<Symbol, Object>] values to store @param previous [State] previous {State}

@return [State] @api private

# File lib/statefully/state.rb, line 213
def initialize(values, previous:)
  @_members = values.freeze
  @previous = previous
end

Public Instance Methods

ancestry() click to toggle source

Return all States that came before

@return [Array<State>] @api public @example

state = Statefully::State.create
=> [#<Statefully::State::None>]
# File lib/statefully/state.rb, line 81
def ancestry
  [previous] + previous.ancestry
end
diff() click to toggle source

Return a {Diff} between current and previous {State}

@return [Diff] @api public @example

Statefully::State.create.succeed(key: 'val').diff
=> #<Statefully::Diff::Changed added={key: "val"}>
# File lib/statefully/state.rb, line 92
def diff
  Diff.create(current: self, previous: previous)
end
failed?() click to toggle source

Check if the current {State} is failed

@return [Boolean] @api public @example

state = Statefully::State.create
state.failed?
=> false

state.fail(RuntimeError.new('Boom!')).failed?
=> true
# File lib/statefully/state.rb, line 133
def failed?
  !successful?
end
finished?() click to toggle source

Check if the current {State} is finished

@return [Boolean] @api public @example

state = Statefully::State.create
state.finished?
=> false

state.finish.finished?
=> true
# File lib/statefully/state.rb, line 148
def finished?
  false
end
history() click to toggle source

Return all historical changes to this {State}

@return [Array<Diff>] @api public @example

Statefully::State.create.succeed(key: 'val').history
=> [#<Statefully::Diff::Changed added={key: "val"}>, #<Statefully::Diff::Created>]
# File lib/statefully/state.rb, line 103
def history
  ([diff] + previous.history).freeze
end
inspect() click to toggle source

Show the current {State} in a human-readable form

@return [String] @api public @example

Statefully::State.create(key: 'val')
=> #<Statefully::State::Success key="val">
# File lib/statefully/state.rb, line 194
def inspect
  _inspect_details({})
end
none?() click to toggle source

Check if the current {State} is none (a null-object of {State})

@return [Boolean] @api public @example

state = Statefully::State.create
state.none?
=> false

state.previous.none?
=> true
# File lib/statefully/state.rb, line 163
def none?
  false
end
resolve() click to toggle source

Resolve the current {State}

Resolving will return the current {State} if successful, but raise an error wrapped in a {State::Failure}. This is a convenience method inspired by monadic composition from functional languages.

@return [State] if the receiver is {#successful?} @raise [StandardError] if the receiver is {#failed?} @api public @example

Statefully::State.create(key: 'val').resolve
=> #<Statefully::State::Success key="val">

Statefully::State.create.fail(RuntimeError.new('Boom!')).resolve
RuntimeError: Boom!
        [STACK TRACE]
# File lib/statefully/state.rb, line 183
def resolve
  self
end
successful?() click to toggle source

Check if the current {State} is successful

@return [Boolean] @api public @example

state = Statefully::State.create
state.successful?
=> true

state.fail(RuntimeError.new('Boom!')).successful?
=> false
# File lib/statefully/state.rb, line 118
def successful?
  true
end

Private Instance Methods

_inspect_details(extras) click to toggle source

Inspect {State} fields, with extras

@param extras [Hash] Non-member values to include

@return [String] @api private

# File lib/statefully/state.rb, line 225
def _inspect_details(extras)
  details = [self.class.name]
  fields = _members.merge(extras)
  details << Inspect.from_fields(fields) unless fields.empty?
  "#<#{details.join(' ')}>"
end
method_missing(name, *args, &block) click to toggle source

Dynamically pass unknown messages to the underlying state storage

State fields become accessible through readers, like in an {ruby-doc.org/stdlib-2.0.0/libdoc/ostruct/rdoc/OpenStruct.html OpenStruct}. A single state field can be questioned for existence by having its name followed by a question mark - eg. bacon?. A single state field can be force-accessed by having its name followed by an exclamation mark - eg. bacon!.

This method reeks of :reek:TooManyStatements.

@param name [Symbol|String] @param args [Array<Object>] @param block [Proc]

@return [Object] @raise [NoMethodError] @raise [Errors::StateMissing] @api private @example

state = Statefully::State.create(bacon: 'tasty')

state.bacon
=> "tasty"

state.bacon?
=> true

state.bacon!
=> "tasty"

state.cabbage
NoMethodError: undefined method `cabbage' for #<Statefully::State::Success bacon="tasty">
        [STACK TRACE]

state.cabbage?
=> false

state.cabbage!
Statefully::Errors::StateMissing: field 'cabbage' missing from state
        [STACK TRACE]
Calls superclass method
# File lib/statefully/state.rb, line 273
def method_missing(name, *args, &block)
  sym_name = name.to_sym
  return fetch(sym_name) if key?(sym_name)
  str_name = name.to_s
  modifier = str_name[-1]
  return super unless %w[? !].include?(modifier)
  base = str_name[0...-1].to_sym
  known = key?(base)
  return known if modifier == '?'
  return fetch(base) if known
  raise Errors::StateMissing, base
end
respond_to_missing?(name, _include_private = false) click to toggle source

Companion to `method_missing`

This method reeks of :reek:BooleanParameter.

@param name [Symbol|String] @param _include_private [Boolean]

@return [Boolean] @api private

Calls superclass method
# File lib/statefully/state.rb, line 295
def respond_to_missing?(name, _include_private = false)
  str_name = name.to_s
  key?(name.to_sym) || %w[? !].any?(&str_name.method(:end_with?)) || super
end