
Security News
Deno 2.2 Improves Dependency Management and Expands Node.js Compatibility
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
StateManager is a state machine implementation for Ruby that is heavily inspired by the FSM implementation in Ember.js. Compared to other FSM implementations, it has the following defining characteristics:
We believe this is an improvement over existing state machines, but just for good measure, check out state_machine and workflow.
Install the state_manager
gem. If you are using bundler, add the following to your Gemfile:
gem 'state_manager'
After the gem is installed, create a file to contain the definition of your state manager, e.g. app/states/post_states.rb
. Edit this file and define your states:
class PostStates < StateManager::Base
state :unsubmitted do
event :submit, :transitions_to => 'submitted.awaiting_review'
end
state :submitted
event :reject, :transitions_to => 'rejected'
state :awaiting_review do
event :review, :transitions_to => 'submitted.reviewing'
end
state :reviewing do
event :accept, :transitions_to => 'active'
event :clarify, :transitions_to => 'submitted.clarifying'
end
state :clarifying do
event :review, :transitions_to => 'submitted.reviewing'
end
end
state :active
state :rejected
end
Once your states are defined, you need to extend the StateManager::Resource
module on your resource class and define a state managed property:
class Post
attr_accessor :state
extend StateManager::Resource
state_manager
end
The code above infers the existence of PostStates
class and a state
property. An alternate states definition and property could be specified as follows:
class Post
attr_accessor :workflow_state
extend StateManager::Resource
state_manager :workflow_state, PostStates
end
Unless otherwise specified with {:helpers => false}
as the third argument to the state_manager
macro, helper methods will be added to the resource class. In the above example, some of the methods that will be available are:
post = Post.new
post.unsubmitted? # true, the post will initially be in the 'unsubmitted' state
post.can_submit? # true, the 'submit' event is defined on the current state
post.can_review? # false, the 'review' event is not defined on the current state
post.submit! # invokes the submit event
post.submitted_awaiting_review? # true, the post is in the 'submitted.awaiting_review' state
post.submitted? # true, the 'submitted' state is a parent of the current state
Most applications will require special logic to be performed during state transitions. Handlers for events can be defined as follows:
class PostStates < StateManager::Base
state :unsubmitted do
event :submit, :transitions_to => 'submitted.awaiting_review'
end
state :submitted
event :reject, :transitions_to => 'rejected'
state :awaiting_review do
event :review, :transitions_to => 'submitted.reviewing'
end
state :reviewing do
event :accept, :transitions_to => 'active'
event :clarify, :transitions_to => 'submitted.clarifying'
end
state :clarifying do
event :review, :transitions_to => 'submitted.reviewing'
end
end
state :active
state :rejected
# Under the hood, the `state` macro creates a class with the same name as the state. Here we add to the definition of that class.
class Unsubmitted
# Defines a handler for the submit event. Events can have arguments
def submit(reason=nil)
# Do something, the post is available as either the `resource` or `post` property
end
end
class Submitted
class AwaitingReview
# Handles the review event. This will *not* handle the review event for the 'submitted.clarifying' state
def review
end
end
# Events on parent states are available to their children.
def reject
end
end
end
As suggested in the above example, states and events really just correspond to classes and methods of the state manager. In fact, the state
macro is really just syntactic sugar around defining a StateManager::State
subclass to the current state--the root state manager is also a state.
On the resource, the state_manager
macro makes an instance of the specified StateManager::Base
subclass available through the "#{property}_manager" attribute on the resource. The above examples of helper methods is essentially syntactic sugar on the following:
post = Post.new
post.state_manager.in_state?('unsubmitted') # true, the post will initially be in the 'unsubmitted' state
post.state_manager.respond_to_event?('submit') # true, the 'submit' event is defined on the current state
post.state_manager.respond_to_event?('review')? # false, the 'review' event is not defined on the current state
post.state_manager.send_event!('submit') # invokes the submit event
post.state_manager.in_state?('submitted.awaiting_review')? # true, the post is in the 'submitted.awaiting_review' state
post.state_manager.in_state?('submitted')? # true, the 'submitted' state is a parent of the current state
Furthermore, only leaf states are valid states for a resource. The state manager can also be explicitly transitioned to a state, however this should normally only be used inside an event handler:
post.state_manager.transition_to('submitted.awaiting_review') # puts the post is in the 'submitted.awaiting_review' state
post.state_manager.transition_to('submitted') # throws a StateManager::InvalidState error, 'submitted' is not a leaf state
By default, the initial state will be the first state that was defined. This can be customized by setting the initial state:
class PostStates < StateManager::Base
initial_state :rejected
...
end
The current state can also be accessed from the state manager:
post.state_manager.current_state.name # 'awaiting_review'
post.state_manager.current_state.path # 'submitted.awaiting_review'
StateManager has several callbacks that can be hooked into:
class PostStates < StateManager::Base
state :unsubmitted do
event :submit, :transitions_to => 'submitted.awaiting_review'
end
state :submitted
# Called when the 'submitted' state is being entered
def enter
end
# Called when the 'submitted' state is being exited.
def exit
end
# After it has been entered
def entered
end
# After it has been exited
def exited
end
event :reject, :transitions_to => 'rejected'
state :awaiting_review do
event :review, :transitions_to => 'submitted.reviewing'
end
...
# Called before every transition
def will_transition(from, to, event)
end
# Called after ever transition
def did_transition(from, to, event)
end
end
In the above example, transitioning between 'submitted.awaiting_review' and 'submitted.reviewing' will not trigger the the enter/exit callbacks for the 'submitted' state, however it will be called for the two sub-states.
StateManager works out of the box with ActiveRecord. enter
, entered
, exit
, and exited
callbacks match up to their equivalent ActiveRecord callbacks: before_save
and after_save
. In addition there are enter_committed
and exit_committed
ActiveRecord-specific callbacks that are triggered when a transtion has been committed to the underlying database. These hooks are useful for actions that are performed external to the database (e.g. enqueuing to an external task worker).
StateManager comes out of the box with support for delayed_job. If delayed_job is available, events can be defined with a :delay
property which indicates a delay after which the event should automatically be triggered:
class UserStates < StateManager::Base
state :submitted do
event :activate, :transitions_to => :active, :delay => 2.days
end
state :active
end
In this example, the activate
event will be called by delayed_job automatically after 2 days unless it is called programatically before then.
Copyright (c) 2012 Gordon Hempton. See LICENSE.txt for further details.
FAQs
Unknown package
We found that state_manager demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
Security News
React's CRA deprecation announcement sparked community criticism over framework recommendations, leading to quick updates acknowledging build tools like Vite as valid alternatives.
Security News
Ransomware payment rates hit an all-time low in 2024 as law enforcement crackdowns, stronger defenses, and shifting policies make attacks riskier and less profitable.