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