Clean and fast Object state transitions in Ruby using the Mixology C extension.
Supports:
-
Dynamic switching between states (mixing and unmixing modules)
-
Clean DSL-style syntax
-
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)
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
}
# methods declared outside a 'state' are not part of any state
def state_entry
puts "entering Default state"
end
def do_something
puts "stares at the ceiling"
end
def state_exit
puts "exiting Default state"
end
# if we want the state_entry to run on instantiation
# we must call it from the initialize method
def initialize
state_entry
end
end
s = Sample.new
# in no state s.do_something #=> “stares at the ceiling”
# 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”
# now switch back to no state s.state nil s.do_something #=> “stares at the ceiling”
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
-
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
s.state Sample::Happy #=> equivalent to above (note the fully qualified name; as Happy is a module defined under the Sample class)
-
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
-
One last note: state(:Name) {} is just DSL-style syntactic sugar for module Name…end