Stateology
Clean and fast Object state transitions in Ruby using the Mixology C extension.
Supports:
- Dynamic switching between states (mixing and unmixing modules)
- Optional state_entry() and state_exit() hooks for each state (automatically called upon state entry and exit)
- support for subclassing of classes that include Stateology (see below)
- support for nested states, i.e states defined within other states
- Clean DSL-style syntax
- NO MAGIC! Stateology does not use any behind-the-scenes method aliasing nor does it make use of any hooks.
Use as in the following:
class Sample
include Stateology
state(:Happy) {
def state_entry
puts "entering Happy state"
end
def do_something
puts "Pets a puppy"
end
def state_exit
puts "exiting Happy state"
end
}
state(:Angry) {
def state_entry
puts "entering Angry state"
end
def do_something
puts "Kicks a puppy"
end
def state_exit
puts "exiting Angry state"
end
}
end
s = Sample.new
# now switch to Happy state
s.state :Happy
s.do_something #=> "Pets a puppy"
# now switch to Angry state
s.state :Angry
s.do_something #=> "Kicks a puppy"
UPDATE:
made it so subclasses can inherit states from their superclasses e.g
class A include Stateology state(:Happy) { def state_entry puts "entering Happy state" end def hello puts "hello from A" end } end class B < A state(:Happy) { def hello puts "hello from B" end } end b = B.new b.state :Happy #=> "entering Happy state" b.hello #=> "hello from B"
prior behaviour was for state_entry not to exist in class B as Happy module from class A was overwritten by the new Happy module in B
how does this fix work? the Happy module in B just includes any extant Happy module accessible in B
A FEW THINGS TO NOTE
When an object is instantiated it begins life in no state and only ordinary instance methods are accessible (The ordinary instance methods are those defined outside of any state() {} block)
The ordinary instance methods are available to any state so long as they are not overridden by the state.
To change from any given state to 'no state' pass nil as a parameter to the state method e.g s.state nil
'no state', while not a state, may nonetheless have state_entry() and state_exit() methods; and these methods will be invoked on 'entry' and exit from 'no state'
The state_entry method for 'no state' is not automatically called on object instantiation. If you wish state_entry to run when the object is instantiated invoke it in the initialize() method.
The state_entry method can also accept parameters: e.g s.state :Happy, "hello" In the above the string "hello" is passed as a parameter to the state_entry() method of the Happy state.
The #state method can accept either a Symbol (e.g :Happy) or a Module (e.g Happy or Sample::Happy). The following are equivalent: s.state :Happy #=> change state to Happy s.state Sample::Happy #=> equivalent to above (note the fully qualified name; as Happy is a module defined under the Sample class)
The #state method can take a block; the block will be executed after the successful change of state: e.g s.state(:Happy) { s.hello } #=> hello method invoked immediately after change of state as it's in the block
alternatively; if the #state method is invoked internally by another instance method of the Sample class then a fully qualified module name is not required: state Happy #=> Fully qualified module name not required when #state invoked in an instance method
The #state method can also act as a 'getter' method when invoked with no parameters. It will return the current state name in Symbol form (e.g :Happy)
The #state_mod method works similarly to the #state 'getter' except it returns the Module representing the current state (e.g Sample::Happy)
The #state?(state_name) returns boolean true if the current state is equal to state_name, and false if not. state_name can be either a Module or a Symbol