Skip to content

low-rb/low_state

Repository files navigation

LowState

A very fast low-level state machine that uses no events for simplicity and speed.

Features:

  • Allowed from transitions
  • Before/after callbacks
  • Before state change guard clause
  • Error raising when can't change state (can be disabled)

In my testing LowState is 10 to 12x faster than State Machines at changing state.

Add State

Supports 3 levels of complexity depending on how simple you want life to be:

🤩 Simplicity

add_state :hole
add_state :seedling
add_state :adult

✨ Functionality

add_state :hole, from: [:hole, :seedling]
add_state :seedling, from: :hole
add_state :adult, from: :seedling

🚀 Complete control

add_state State.new(state: :hole, from: [:hole, :seedling], before: 'custom_callback_name')
add_state State.new(state: :seedling, from: :hole, error: false)
add_state CustomState.new(state: :adult, from: :seedling)

Set State

alias: low_state=()

Change state with:

state = :seedling

Get State

alias: low_state

Get the current state with:

state

Config

The first add_state defined is the default state.

Configuration options for each state:

  • from: - The allowed from transitions
  • before: - The method name of the before callback
  • after: - The method name of the after callback
  • error: - Whether or not to error when state does not transition

Before/After Callbacks

Callback methods with the following naming pattern and are automatically available to use:

  • [state_name]_state_before
  • [state_name]_state_after

Before

def seed_state_before
  # Do stuff before the state has changed.
  # Return `false` if you would like to abort the state change.
end

After

def seed_state_after
  # Do stuff after the state has changed.
  # The return value here becomes the return value of change_state().
end

Arguments

If extra arguments are passed to change_state() then they will be available in the before/after callbacks:

# Called with extra argument.
change_state(:seedling, my_arg)

# Argument passed to callback.
def seed_state_before(my_arg)
  puts my_arg
end

NOTE: Once extra arguments are passed to change_state() then every before/after callback must now support receiving arguments or you'll get an "Expected Argument" error.

Full Example

# Define the class with state:
class Cell
  include LowState

  add_state :hole, from: [:hole, :seedling]
  add_state :seedling, from: :hole, error: false
  add_state :adult, from: :seedling

  # Fire methods before and after state change:
  def seed_state_before(my_arg)
    puts my_arg # => My argument
  end

  def seed_state_after(my_arg)
    "We did it!"
  end
end

# Create the instance:
cell = Cell.new

# Change state on the instance:
result = cell.state = :seedling, "My argument" # => "We did it!"

# Access state on the instance:
cell.state # => :seedling

Philosophy

📣 Events. Why have events? Seriously. I was using events with state machines and all it ended up making me do was think of duplicate names for things. Besides, the events are usually what your methods that enact the state change are called anyway (method: verb, state: noun). Or you use an event-driven architecture and now you have a double up of what an event means. LowState gets out of your way.

🚘 Speed. Less is more, there's so much code going on in other state machine libraries and there really is no need. It also makes them hard to debug. All we're doing is changing a state/symbol on an object.

🌹 Name. The name LowState represents my ever-lasting depression and I hope this tool will guide you out of yours too.

About

A very fast low-level state machine

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors