class Statefully::State
{State} is an immutable collection of fields with some convenience methods. @abstract
Attributes
State
fields
@return [Hash] @api private
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 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
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
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
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
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
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
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
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
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 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
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
{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
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]
# 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
Companion to `method_missing`
This method reeks of :reek:BooleanParameter.
@param name [Symbol|String] @param _include_private [Boolean]
@return [Boolean] @api private
# 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