lib/simply_fsm.rb in simply_fsm-0.1.2 vs lib/simply_fsm.rb in simply_fsm-0.2.0
- old
+ new
@@ -7,10 +7,21 @@
module SimplyFSM
def self.included(base)
base.extend(ClassMethods)
end
+ def state_match?(from, current)
+ return true if from == :any
+ return from.include?(current) if from.is_a?(Array)
+
+ from == current
+ end
+
+ def cannot_transition?(from, cond, current)
+ (from && !state_match?(from, current)) || (cond && !instance_exec(&cond))
+ end
+
##
# Defines the constructor for defining a state machine
module ClassMethods
##
# Declare a state machine called +name+ which can then be defined
@@ -53,30 +64,75 @@
send(state_machine_name) == status
}
end
##
- # Define an event by +event_name+ and
- # - its +transition+ as a hash with a +from+ state or array of states and the +to+ state,
+ # Define an event by +event_name+
+ #
+ # - which +transitions+ as a hash with a +from+ state or array of states and the +to+ state,
# - an optional +guard+ lambda which must return true for the transition to occur,
# - an optional +fail+ lambda that is called when the transition fails (overrides top-level fail handler), and
# - an optional do block that is called +after+ the transition succeeds
- def event(event_name, transition:, guard: nil, fail: nil, &after)
- return unless event_exists?(event_name) && transition
+ def event(event_name, transitions:, guard: nil, fail: nil, &after)
+ return unless event_exists?(event_name) && transitions
@events << event_name
- to = transition[:to]
may_event_name = "may_#{event_name}?"
- setup_may_event_method may_event_name, transition[:from], to, guard
+ if transitions.is_a?(Array)
+ setup_multi_transition_may_event_method transitions: transitions, guard: guard,
+ may_event_name: may_event_name
+ setup_multi_transition_event_method event_name,
+ transitions: transitions, guard: guard,
+ var_name: "@#{@name}", fail: fail || @fail_handler
+ return
+ end
+
+ to = transitions[:to]
+ setup_may_event_method may_event_name, transitions[:from] || :any, transitions[:when], guard
setup_event_method event_name, var_name: "@#{@name}",
may_event_name: may_event_name, to: to,
fail: fail || @fail_handler, &after
end
private
+ def setup_multi_transition_may_event_method(transitions:, guard:, may_event_name:)
+ state_machine_name = @name
+
+ make_owner_method may_event_name, lambda {
+ if !guard || instance_exec(&guard)
+ current = send(state_machine_name)
+ # Check each transition, and first one that succeeds ends the scan
+ transitions.each do |t|
+ next if cannot_transition?(t[:from], t[:when], current)
+
+ return true
+ end
+ end
+ false
+ }
+ end
+
+ def setup_multi_transition_event_method(event_name, transitions:, guard:, var_name:, fail:)
+ state_machine_name = @name
+ make_owner_method event_name, lambda {
+ if !guard || instance_exec(&guard)
+ current = send(state_machine_name)
+ # Check each transition, and first one that succeeds ends the scan
+ transitions.each do |t|
+ next if cannot_transition?(t[:from], t[:when], current)
+
+ instance_variable_set(var_name, t[:to])
+ return true
+ end
+ end
+ instance_exec(&fail) if fail
+ false
+ }
+ end
+
def event_exists?(event_name)
event_name && !@events.include?(event_name)
end
def setup_event_method(event_name, var_name:, may_event_name:, to:, fail:, &after)
@@ -97,36 +153,70 @@
false
}
make_owner_method event_name, method_lambda
end
- def setup_may_event_method(may_event_name, from, _to, guard)
+ def setup_may_event_method(may_event_name, from, cond, guard)
state_machine_name = @name
#
# Instead of one "may_event?" method that checks all variations every time it's called, here we check
# the event definition and define the most optimal lambda to ensure the check is as fast as possible
- method_lambda = if from == :any && !guard
- -> { true } # unguarded transition from any state
- elsif from == :any
- guard # guarded transition from any state
- elsif !guard
- guardless_may_event_lambda(from, state_machine_name)
+ method_lambda = if from == :any
+ from_any_may_event_lambda(guard, cond, state_machine_name)
else
- guarded_may_event_lambda(from, guard, state_machine_name)
+ guarded_or_conditional_may_event_lambda(from, guard, cond, state_machine_name)
end
make_owner_method may_event_name, method_lambda
end
+ def from_any_may_event_lambda(guard, cond, _state_machine_name)
+ if !guard && !cond
+ -> { true } # unguarded transition from any state
+ elsif !cond
+ guard # guarded transition from any state
+ elsif !guard
+ cond # conditional unguarded transition from any state
+ else
+ -> { instance_exec(&guard) && instance_exec(&cond) }
+ end
+ end
+
+ def guarded_or_conditional_may_event_lambda(from, guard, cond, state_machine_name)
+ if !guard && !cond
+ guardless_may_event_lambda(from, state_machine_name)
+ elsif !cond
+ guarded_may_event_lambda(from, guard, state_machine_name)
+ elsif !guard
+ guarded_may_event_lambda(from, cond, state_machine_name)
+ else
+ guarded_and_conditional_may_event_lambda(from, guard, cond, state_machine_name)
+ end
+ end
+
def guarded_may_event_lambda(from, guard, state_machine_name)
if from.is_a?(Array)
lambda { # guarded transition from choice of states
current = send(state_machine_name)
from.include?(current) && instance_exec(&guard)
}
else
lambda { # guarded transition from one state
current = send(state_machine_name)
from == current && instance_exec(&guard)
+ }
+ end
+ end
+
+ def guarded_and_conditional_may_event_lambda(from, guard, cond, state_machine_name)
+ if from.is_a?(Array)
+ lambda { # guarded transition from choice of states
+ current = send(state_machine_name)
+ from.include?(current) && instance_exec(&guard) && instance_exec(&cond)
+ }
+ else
+ lambda { # guarded transition from one state
+ current = send(state_machine_name)
+ from == current && instance_exec(&guard) && instance_exec(&cond)
}
end
end
def guardless_may_event_lambda(from, state_machine_name)