Skip to content

Commit

Permalink
Add when and events methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
soveran committed Mar 29, 2012
1 parent 61aaceb commit a8815e6
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 29 deletions.
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ all provide a nice DSL for declaring events, exceptions, callbacks,
and all kinds of niceties in general.

But if all you want is a finite state machine, look no further: this
is only 22 lines of code and provides everything a finite state
has less than 50 lines of code and provides everything a finite state
machine must have, and nothing more.

Usage
Expand All @@ -22,9 +22,10 @@ require 'micromachine'

machine = MicroMachine.new(:new) # Initial state.

machine.transitions_for[:confirm] = { :new => :confirmed }
machine.transitions_for[:ignore] = { :new => :ignored }
machine.transitions_for[:reset] = { :confirmed => :new, :ignored => :new }
# Define the possible transitions for each event.
machine.when(:confirm, :new => :confirmed)
machine.when(:ignore, :new => :ignored)
machine.when(:reset, :confirmed => :new, :ignored => :new)

machine.trigger(:confirm) #=> true
machine.state #=> :confirmed
Expand All @@ -39,6 +40,15 @@ machine.trigger(:ignore) #=> true
machine.state #=> :ignored
```

The `when` helper is syntactic sugar for assigning to the
`transitions_for` hash. This code is equivalent:

``` ruby
machine.transitions_for[:confirm] = { :new => :confirmed }
machine.transitions_for[:ignore] = { :new => :ignored }
machine.transitions_for[:reset] = { :confirmed => :new, :ignored => :new }
```

You can also ask if an event will trigger a change in state. Following
the example above:

Expand Down Expand Up @@ -103,9 +113,9 @@ class Event < ActiveRecord::Base
@confirmation ||= begin
fsm = MicroMachine.new(confirmation_state || "pending")

fsm.transitions_for[:confirm] = { "pending" => "confirmed" }
fsm.transitions_for[:cancel] = { "confirmed" => "cancelled" }
fsm.transitions_for[:reset] = { "confirmed" => "pending", "cancelled" => "pending" }
fsm.when(:confirm, "pending" => "confirmed")
fsm.when(:cancel, "confirmed" => "cancelled")
fsm.when(:reset, "confirmed" => "pending", "cancelled" => "pending")

fsm
end
Expand Down Expand Up @@ -143,9 +153,9 @@ class Event < ActiveRecord::Base
@confirmation ||= begin
fsm = MicroMachine.new(confirmation_state || "pending")

fsm.transitions_for[:confirm] = { "pending" => "confirmed" }
fsm.transitions_for[:cancel] = { "confirmed" => "cancelled" }
fsm.transitions_for[:reset] = { "confirmed" => "pending", "cancelled" => "pending" }
fsm.when(:confirm, "pending" => "confirmed")
fsm.when(:cancel, "confirmed" => "cancelled")
fsm.when(:reset, "confirmed" => "pending", "cancelled" => "pending")

fsm.on(:any) { self.confirmation_state = confirmation.state }

Expand Down
6 changes: 3 additions & 3 deletions examples/advanced.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

fsm = MicroMachine.new(:pending)

fsm.transitions_for[:confirm] = { :pending => :confirmed }
fsm.transitions_for[:ignore] = { :pending => :ignored }
fsm.transitions_for[:reset] = { :confirmed => :pending, :ignored => :pending }
fsm.when(:confirm, :pending => :confirmed)
fsm.when(:ignore, :pending => :ignored)
fsm.when(:reset, :confirmed => :pending, :ignored => :pending)

puts "Should print Confirmed, Pending and Ignored:"

Expand Down
6 changes: 3 additions & 3 deletions examples/basic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

fsm = MicroMachine.new(:pending)

fsm.transitions_for[:confirm] = { :pending => :confirmed }
fsm.transitions_for[:ignore] = { :pending => :ignored }
fsm.transitions_for[:reset] = { :confirmed => :pending, :ignored => :pending }
fsm.when(:confirm, :pending => :confirmed)
fsm.when(:ignore, :pending => :ignored)
fsm.when(:reset, :confirmed => :pending, :ignored => :pending)

puts "Should print Confirmed, Reset and Ignored:"

Expand Down
6 changes: 3 additions & 3 deletions examples/callbacks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

fsm = MicroMachine.new(:pending)

fsm.transitions_for[:confirm] = { :pending => :confirmed }
fsm.transitions_for[:ignore] = { :pending => :ignored }
fsm.transitions_for[:reset] = { :confirmed => :pending, :ignored => :pending }
fsm.when(:confirm, :pending => :confirmed)
fsm.when(:ignore, :pending => :ignored)
fsm.when(:reset, :confirmed => :pending, :ignored => :pending)

puts "Should print Confirmed, Reset and Ignored:"

Expand Down
8 changes: 8 additions & 0 deletions lib/micromachine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ def on key, &block
@callbacks[key] << block
end

def when(event, transitions)
transitions_for[event] = transitions
end

def trigger event
if trigger?(event)
@state = transitions_for[event][@state]
Expand All @@ -31,6 +35,10 @@ def trigger?(event)
raise InvalidEvent
end

def events
transitions_for.keys
end

def ==(some_state)
state == some_state
end
Expand Down
47 changes: 37 additions & 10 deletions test/all_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,39 @@ class MicroMachineTest < Test::Unit::TestCase
end
end

context "using when for defining transitions" do
setup do
@machine = MicroMachine.new(:pending)
@machine.when(:confirm, :pending => :confirmed)
@machine.when(:ignore, :pending => :ignored)
@machine.when(:reset, :confirmed => :pending, :ignored => :pending)
end

should "discern transitions" do
assert_equal true, @machine.trigger?(:confirm)
assert_equal true, @machine.trigger(:confirm)
assert_equal :confirmed, @machine.state

assert_equal false, @machine.trigger?(:ignore)
assert_equal false, @machine.trigger(:ignore)
assert_equal :confirmed, @machine.state

assert_equal true, @machine.trigger?(:reset)
assert_equal true, @machine.trigger(:reset)
assert_equal :pending, @machine.state

assert_equal true, @machine.trigger?(:ignore)
assert_equal true, @machine.trigger(:ignore)
assert_equal :ignored, @machine.state
end
end

context "dealing with callbacks" do
setup do
@machine = MicroMachine.new(:pending)
@machine.transitions_for[:confirm] = { :pending => :confirmed }
@machine.transitions_for[:ignore] = { :pending => :ignored }
@machine.transitions_for[:reset] = { :confirmed => :pending, :ignored => :pending }
@machine.when(:confirm, :pending => :confirmed)
@machine.when(:ignore, :pending => :ignored)
@machine.when(:reset, :confirmed => :pending, :ignored => :pending)

@machine.on(:pending) { @state = "Pending" }
@machine.on(:confirmed) { @state = "Confirmed" }
Expand Down Expand Up @@ -80,13 +107,13 @@ class Model

def machine
@machine ||= begin
machine = MicroMachine.new(:pending)
machine.transitions_for[:confirm] = { :pending => :confirmed }
machine.transitions_for[:ignore] = { :pending => :ignored }
machine.transitions_for[:reset] = { :confirmed => :pending, :ignored => :pending }
machine.on(:any) { self.state = machine.state }
machine
end
machine = MicroMachine.new(:pending)
machine.when(:confirm, :pending => :confirmed)
machine.when(:ignore, :pending => :ignored)
machine.when(:reset, :confirmed => :pending, :ignored => :pending)
machine.on(:any) { self.state = machine.state }
machine
end
end
end

Expand Down

0 comments on commit a8815e6

Please sign in to comment.