Refactor FSM#transition #99

Merged
merged 6 commits into from Oct 5, 2012
View
@@ -84,30 +84,71 @@ def attach(actor)
#
# Note: making additional state transitions will cancel delayed transitions
def transition(state_name, options = {})
+ new_state = validate_and_sanitize_new_state(state_name)
+ return unless new_state
+
+ if handle_delayed_transitions(new_state, options[:delay])
+ return @delayed_transition
+ end
+
+ transition_with_callbacks!(new_state)
+ end
+
+ # Immediate state transition with no sanity checks, or callbacks. "Dangerous!"
+ def transition!(state_name)
+ @state = state_name
+ end
+
+ protected
+
+ def validate_and_sanitize_new_state(state_name)
state_name = state_name.to_sym
- current_state = self.class.states[@state]
- return if current_state && current_state.name == state_name
+ return if current_state_name == state_name
if current_state and not current_state.valid_transition? state_name
valid = current_state.transitions.map(&:to_s).join(", ")
raise ArgumentError, "#{self.class} can't change state from '#{@state}' to '#{state_name}', only to: #{valid}"
end
- new_state = self.class.states[state_name]
+ new_state = states[state_name]
unless new_state
- return if state_name == self.class.default_state
+ return if state_name == default_state
raise ArgumentError, "invalid state for #{self.class}: #{state_name}"
end
- if options[:delay]
+ new_state
+ end
+
+ def transition_with_callbacks!(state_name)
+ transition! state_name.name
+ state_name.call(self)
+ end
+
+ def states
+ self.class.states
+ end
+
+ def default_state
+ self.class.default_state
+ end
+
+ def current_state
+ states[@state]
+ end
+
+ def current_state_name
+ current_state && current_state.name || ''
+ end
+
+ def handle_delayed_transitions(new_state, delay)
+ if delay
raise UnattachedError, "can't delay unless attached" unless @actor
@delayed_transition.cancel if @delayed_transition
- @delayed_transition = @actor.after(options[:delay]) do
- transition! new_state.name
- new_state.call(self)
+ @delayed_transition = @actor.after(delay) do
+ transition_with_callbacks!(new_state)
end
return @delayed_transition
@@ -117,14 +158,6 @@ def transition(state_name, options = {})
@delayed_transition.cancel
@delayed_transition = nil
end
-
- transition! new_state.name
- new_state.call(self)
- end
-
- # Immediate state transition with no sanity checks. "Dangerous!"
- def transition!(state_name)
- @state = state_name
end
# FSM states as declared by Celluloid::FSM.state
@@ -83,4 +83,28 @@ class CustomDefaultMachine
subject.state.should == :pre_done
end
+
+ context "actor is not set" do
+ let(:subject) { TestMachine.new }
+
+ context "transition is delayed" do
+ it "raises an unattached error" do
+ expect { subject.transition :another, :delay => 100 } \
+ .to raise_error(Celluloid::FSM::UnattachedError)
+ end
+ end
+ end
+
+ context "transitioning to an invalid state" do
+ let(:subject) { TestMachine.new }
+
+ it "raises an argument error" do
+ expect { subject.transition :invalid_state }.to raise_error(ArgumentError)
+ end
+
+ it "should not call transition! if the state is :default" do
+ subject.should_not_receive :transition!
+ subject.transition :default
+ end
+ end
end