Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor FSM#transition #99

Merged
merged 6 commits into from Oct 5, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 49 additions & 16 deletions lib/celluloid/fsm.rb
Expand Up @@ -84,30 +84,71 @@ def attach(actor)
# #
# Note: making additional state transitions will cancel delayed transitions # Note: making additional state transitions will cancel delayed transitions
def transition(state_name, options = {}) 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 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 if current_state and not current_state.valid_transition? state_name
valid = current_state.transitions.map(&:to_s).join(", ") valid = current_state.transitions.map(&:to_s).join(", ")
raise ArgumentError, "#{self.class} can't change state from '#{@state}' to '#{state_name}', only to: #{valid}" raise ArgumentError, "#{self.class} can't change state from '#{@state}' to '#{state_name}', only to: #{valid}"
end end


new_state = self.class.states[state_name] new_state = states[state_name]


unless new_state 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}" raise ArgumentError, "invalid state for #{self.class}: #{state_name}"
end 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 raise UnattachedError, "can't delay unless attached" unless @actor
@delayed_transition.cancel if @delayed_transition @delayed_transition.cancel if @delayed_transition


@delayed_transition = @actor.after(options[:delay]) do @delayed_transition = @actor.after(delay) do
transition! new_state.name transition_with_callbacks!(new_state)
new_state.call(self)
end end


return @delayed_transition return @delayed_transition
Expand All @@ -117,14 +158,6 @@ def transition(state_name, options = {})
@delayed_transition.cancel @delayed_transition.cancel
@delayed_transition = nil @delayed_transition = nil
end 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 end


# FSM states as declared by Celluloid::FSM.state # FSM states as declared by Celluloid::FSM.state
Expand Down
24 changes: 24 additions & 0 deletions spec/celluloid/fsm_spec.rb
Expand Up @@ -83,4 +83,28 @@ class CustomDefaultMachine


subject.state.should == :pre_done subject.state.should == :pre_done
end 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 end