simplificator-fsm 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +1 -1
- data/README.rdoc +2 -2
- data/VERSION.yml +1 -1
- data/lib/fsm.rb +15 -16
- data/lib/fsm/builder.rb +52 -0
- data/lib/fsm/executable.rb +14 -7
- data/lib/fsm/machine.rb +37 -44
- data/lib/fsm/options.rb +17 -0
- data/lib/fsm/state.rb +17 -6
- data/lib/fsm/transition.rb +6 -2
- data/test/executable_test.rb +33 -0
- data/test/invoice_sample_test.rb +67 -0
- data/test/options_test.rb +37 -0
- data/test/state_test.rb +21 -0
- data/test/transition_test.rb +38 -0
- metadata +14 -5
- data/lib/fsm/machine_config.rb +0 -21
- data/test/fsm_test.rb +0 -7
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
data/VERSION.yml
CHANGED
data/lib/fsm.rb
CHANGED
@@ -1,33 +1,32 @@
|
|
1
|
-
|
2
|
-
require File.join(File.dirname(__FILE__), 'fsm',
|
3
|
-
|
4
|
-
require File.join(File.dirname(__FILE__), 'fsm', 'state')
|
5
|
-
require File.join(File.dirname(__FILE__), 'fsm', 'transition')
|
6
|
-
require File.join(File.dirname(__FILE__), 'fsm', 'executable')
|
1
|
+
%w[options errors machine state transition executable builder].each do |item|
|
2
|
+
require File.join(File.dirname(__FILE__), 'fsm', item)
|
3
|
+
end
|
7
4
|
|
8
5
|
module FSM
|
9
6
|
module ClassMethods
|
10
7
|
def define_fsm(&block)
|
11
|
-
raise
|
12
|
-
|
13
|
-
Machine[self] =
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
raise 'FSM is already defined. Call define_fsm only once' if Machine[self]
|
9
|
+
builder = Builder.new(self)
|
10
|
+
Machine[self] = builder.process(&block)
|
11
|
+
self.instance_eval() do
|
12
|
+
alias_method "fsm_state_attribute", Machine[self].current_state_attribute_name
|
13
|
+
define_method(Machine[self].current_state_attribute_name) do
|
14
|
+
value = fsm_state_attribute
|
15
|
+
value ? value : Machine[self.class].initial_state_name
|
16
|
+
end
|
17
|
+
end
|
17
18
|
end
|
18
|
-
|
19
|
-
|
20
19
|
end
|
21
20
|
|
22
21
|
module InstanceMethods
|
23
22
|
#
|
24
23
|
# Which states are reachable from the current state
|
25
24
|
def reachable_state_names
|
26
|
-
Machine[self.class].reachable_states.map() {|item| item.name}
|
25
|
+
Machine[self.class].reachable_states(self).map() {|item| item.name}
|
27
26
|
end
|
28
27
|
|
29
28
|
def available_transition_names
|
30
|
-
Machine[self.class].available_transitions.map() {|item| item.name}
|
29
|
+
Machine[self.class].available_transitions(self).map() {|item| item.name}
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
data/lib/fsm/builder.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module FSM
|
2
|
+
# Builder exposees 'only' (well there are some other methods exposed) the methods that are required to build the configuration
|
3
|
+
class Builder
|
4
|
+
|
5
|
+
# Blank Slate
|
6
|
+
instance_methods.each do |m|
|
7
|
+
undef_method m unless m == '__send__' || m == '__id__' || m == 'instance_eval'
|
8
|
+
end
|
9
|
+
|
10
|
+
# Create a new Builder which creates a Machine for the target_class
|
11
|
+
def initialize(target_class)
|
12
|
+
@target_class = target_class
|
13
|
+
@machine = Machine.new(target_class)
|
14
|
+
end
|
15
|
+
|
16
|
+
def process(&block)
|
17
|
+
raise ArgumentError.new('Block expected') unless block_given?
|
18
|
+
self.instance_eval(&block)
|
19
|
+
@machine
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
# Add a transition
|
24
|
+
# * name of the transition
|
25
|
+
# * from_name: name of the source state (symbol)
|
26
|
+
# * to_name: name of the target state (symbol)
|
27
|
+
# * options
|
28
|
+
#
|
29
|
+
def transition(name, from_name, to_name, options = {})
|
30
|
+
@machine.transition(name, from_name, to_name, options)
|
31
|
+
nil # do not expose FSM details
|
32
|
+
end
|
33
|
+
|
34
|
+
def state_attribute(name)
|
35
|
+
raise ArgumentError.new('Invalid attribute name') if name == nil
|
36
|
+
@machine.current_state_attribute_name = name
|
37
|
+
nil # do not expose FSM details
|
38
|
+
end
|
39
|
+
|
40
|
+
def initial(name)
|
41
|
+
@machine.initial_state_name = name
|
42
|
+
nil # do not expose FSM details
|
43
|
+
end
|
44
|
+
|
45
|
+
def state(name, options = {})
|
46
|
+
@machine.state(name, options)
|
47
|
+
nil # do not expose FSM details
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
data/lib/fsm/executable.rb
CHANGED
@@ -2,24 +2,31 @@ module FSM
|
|
2
2
|
#
|
3
3
|
# Execute an action specified by either String, Sylbol or Proc.
|
4
4
|
# Symbol and String represent methods which are called on the target object, Proc will get executed
|
5
|
-
# and receives at least the target as parameter
|
5
|
+
# and receives at least the target as parameter. If others parameters are passed then they'll get forwarded as well.
|
6
6
|
class Executable
|
7
7
|
# Create a new Executable
|
8
8
|
# if args is true, then arguments are passed on to the target method or the Proc, if false nothing
|
9
9
|
# will get passed
|
10
|
-
def initialize(thing
|
10
|
+
def initialize(thing)
|
11
|
+
raise ArgumentError.new("Unknown thing #{thing}") unless thing
|
11
12
|
@thing = thing
|
12
|
-
@has_arguments = args
|
13
13
|
end
|
14
14
|
|
15
15
|
# execute this executable on the given target
|
16
|
-
def execute(target, args)
|
16
|
+
def execute(target, *args)
|
17
17
|
case @thing
|
18
18
|
when String, Symbol:
|
19
|
-
|
19
|
+
if (args.length > 0)
|
20
|
+
target.send(@thing, *args)
|
21
|
+
else
|
22
|
+
target.send(@thing)
|
23
|
+
end
|
20
24
|
when Proc:
|
21
|
-
|
22
|
-
|
25
|
+
if (args.length > 0)
|
26
|
+
@thing.call(target, *args)
|
27
|
+
else
|
28
|
+
@thing.call(target)
|
29
|
+
end
|
23
30
|
else
|
24
31
|
raise "Unknown Thing #{@thing}"
|
25
32
|
end
|
data/lib/fsm/machine.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module FSM
|
2
2
|
class Machine
|
3
|
-
attr_accessor(:
|
3
|
+
attr_accessor(:initial_state_name, :current_state_attribute_name, :states)
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
5
|
+
def initialize(target_class)
|
6
|
+
@target_class = target_class
|
7
7
|
self.states = {}
|
8
|
-
self.
|
8
|
+
self.current_state_attribute_name = :state
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.[](includer)
|
@@ -19,76 +19,69 @@ module FSM
|
|
19
19
|
def state(name, options = {})
|
20
20
|
raise "State is already defined: '#{name}'" if self.states[name]
|
21
21
|
self.states[name] = State.new(name, options)
|
22
|
-
|
23
|
-
nil
|
22
|
+
self.initial_state_name=(name) unless self.initial_state_name
|
24
23
|
end
|
25
24
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
nil
|
25
|
+
def initial_state_name=(value)
|
26
|
+
raise UnknownState.new("Unknown state '#{value}'. Define states first with state(name)") unless self.states[value]
|
27
|
+
@initial_state_name = value
|
30
28
|
end
|
31
29
|
|
32
30
|
def transition(name, from_name, to_name, options = {})
|
33
31
|
raise ArgumentError.new("name, from_name and to_name are required") if name.nil? || from_name.nil? || to_name.nil?
|
34
32
|
raise UnknownState.new("Unknown source state '#{from}'") unless self.states[from_name]
|
35
33
|
raise UnknownState.new("Unknown target state '#{to}'") unless self.states[to_name]
|
36
|
-
|
34
|
+
|
37
35
|
from_state = self.states[from_name]
|
38
36
|
to_state = self.states[to_name]
|
37
|
+
|
39
38
|
transition = Transition.new(name, from_state, to_state, options)
|
40
|
-
from_state.
|
41
|
-
|
39
|
+
from_state.add_transition(transition)
|
40
|
+
|
41
|
+
define_transition_method(name, to_name)
|
42
|
+
|
42
43
|
end
|
43
44
|
|
44
|
-
def
|
45
|
-
|
45
|
+
def self.get_current_state_name(target)
|
46
|
+
value = target.send(Machine[target.class].current_state_attribute_name)
|
47
|
+
(value && value.is_a?(String)) ? value.intern : value
|
46
48
|
end
|
47
49
|
|
48
|
-
def
|
49
|
-
|
50
|
-
state.to_states if state == current_state
|
51
|
-
end.flatten.compact
|
50
|
+
def self.set_current_state_name(target, value)
|
51
|
+
target.send("#{Machine[target.class].current_state_attribute_name}=", value)
|
52
52
|
end
|
53
|
+
|
53
54
|
|
54
|
-
def
|
55
|
-
|
55
|
+
def reachable_states(target)
|
56
|
+
reachables = []
|
57
|
+
current_state_name = Machine.get_current_state_name(target)
|
58
|
+
self.states.map do |name, state|
|
59
|
+
reachables += state.to_states if state.name == current_state_name
|
60
|
+
end
|
61
|
+
reachables
|
56
62
|
end
|
57
63
|
|
58
|
-
|
59
|
-
|
60
|
-
define_state_attribute_methods(self.state_attribute)
|
64
|
+
def available_transitions(target)
|
65
|
+
self.states[Machine.get_current_state_name(target)].transitions.values
|
61
66
|
end
|
62
67
|
|
63
|
-
|
64
|
-
|
65
68
|
private
|
66
69
|
|
67
|
-
def define_state_attribute_methods(name)
|
68
|
-
@klass.instance_eval do
|
69
|
-
define_method("#{name}") do
|
70
|
-
Machine[self.class].current_state.name
|
71
|
-
end
|
72
|
-
|
73
|
-
define_method("#{name}=") do |value|
|
74
|
-
Machine[self.class].current_state = Machine[self.class].states[value]
|
75
|
-
raise("Unknown State #{value}") unless Machine[self.class].current_state
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
70
|
def define_transition_method(name, to_name)
|
81
|
-
@
|
71
|
+
@target_class.instance_eval do
|
82
72
|
define_method(name) do |*args|
|
83
|
-
|
84
|
-
|
73
|
+
machine = Machine[self.class]
|
74
|
+
from_name = Machine.get_current_state_name(self)
|
75
|
+
from_state = machine.states[from_name]
|
76
|
+
to_state = machine.states[to_name]
|
85
77
|
transition = from_state.transitions[to_name]
|
86
|
-
raise InvalidStateTransition.new("No transition defined from #{
|
78
|
+
raise InvalidStateTransition.new("No transition defined from #{from_name} -> #{to_name}") unless transition
|
87
79
|
|
88
80
|
from_state.exit(self)
|
89
81
|
transition.fire_event(self, args)
|
90
82
|
to_state.enter(self)
|
91
|
-
Machine
|
83
|
+
Machine.set_current_state_name(self, to_name)
|
84
|
+
true # at the moment always return true ... as soon as we have guards or thelike this could be false as well
|
92
85
|
end
|
93
86
|
end
|
94
87
|
end
|
data/lib/fsm/options.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module FSM
|
2
|
+
module Options
|
3
|
+
module InstanceMethods
|
4
|
+
def assert_options(options, optional_keys = {}, mandatory_keys = {})
|
5
|
+
keys_processed = []
|
6
|
+
mandatory_keys.each do |key|
|
7
|
+
raise ArgumentError.new("Mandatory Key #{key} is missing") unless options.keys.include?(key)
|
8
|
+
keys_processed << key
|
9
|
+
end
|
10
|
+
options.keys.each do |key|
|
11
|
+
raise ArgumentError.new("Unsupported key #{key}") unless optional_keys.include?(key) || mandatory_keys.include?(key)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/lib/fsm/state.rb
CHANGED
@@ -3,6 +3,7 @@ module FSM
|
|
3
3
|
# A State has a name and a list of outgoing transitions.
|
4
4
|
#
|
5
5
|
class State
|
6
|
+
include FSM::Options::InstanceMethods
|
6
7
|
attr_reader(:name, :transitions)
|
7
8
|
|
8
9
|
# name: a symbol which identifies this state
|
@@ -10,28 +11,38 @@ module FSM
|
|
10
11
|
# * :enter : a symbol or string or Proc
|
11
12
|
# * :exit : a symbol or string or Proc
|
12
13
|
def initialize(name, options = {})
|
14
|
+
raise ArgumentError.new('Name is required') unless name
|
15
|
+
assert_options(options, [:enter, :exit])
|
13
16
|
@name = name
|
14
|
-
@enter = Executable.new options[:enter]
|
15
|
-
@exit = Executable.new options[:exit]
|
17
|
+
@enter = Executable.new options[:enter] if options.has_key?(:enter)
|
18
|
+
@exit = Executable.new options[:exit] if options.has_key?(:exit)
|
16
19
|
@transitions = {}
|
17
20
|
end
|
18
21
|
|
19
22
|
# Called when this state is entered
|
20
23
|
def enter(target)
|
21
|
-
@enter.execute(target
|
24
|
+
@enter.execute(target) if @enter
|
25
|
+
nil
|
22
26
|
end
|
23
27
|
# Called when this state is exited
|
24
28
|
def exit(target)
|
25
|
-
@exit.execute(target
|
29
|
+
@exit.execute(target) if @exit
|
30
|
+
nil
|
26
31
|
end
|
27
32
|
|
33
|
+
def add_transition(transition)
|
34
|
+
raise ArgumentError.new("#{self} already has a transition to '#{transition.name}'") if @transitions.has_key?(transition.name)
|
35
|
+
raise ArgumentError.new("the transition '#{transition.name}' is already defined") if @transitions.detect() {|to_name, tr| transition.name == tr.name}
|
36
|
+
@transitions[transition.to.name] = transition
|
37
|
+
end
|
38
|
+
|
28
39
|
# All states that are reachable form this state by one hop
|
29
40
|
def to_states
|
30
|
-
|
41
|
+
@transitions.map { |to_name, transition| transition.to}
|
31
42
|
end
|
32
43
|
|
33
44
|
def to_s
|
34
|
-
"State '#{self.name}'
|
45
|
+
"State '#{self.name}'"
|
35
46
|
end
|
36
47
|
|
37
48
|
end
|
data/lib/fsm/transition.rb
CHANGED
@@ -1,16 +1,20 @@
|
|
1
1
|
module FSM
|
2
2
|
class Transition
|
3
|
+
include FSM::Options::InstanceMethods
|
3
4
|
attr_accessor(:name, :from, :to, :event)
|
4
5
|
def initialize(name, from, to, options = {})
|
6
|
+
raise ArgumentError.new("name, from and to are required but were '#{name}', '#{from}' and '#{to}'") unless name && from && to
|
7
|
+
assert_options(options, [:event])
|
5
8
|
self.name = name
|
6
9
|
self.from = from
|
7
10
|
self.to = to
|
8
|
-
self.event = Executable.new options[:event]
|
11
|
+
self.event = Executable.new options[:event] if options.has_key?(:event)
|
9
12
|
end
|
10
13
|
|
11
14
|
def fire_event(target, args)
|
12
|
-
self.event.execute(target, args)
|
15
|
+
self.event.execute(target, *args) if self.event
|
13
16
|
end
|
17
|
+
|
14
18
|
def to_s
|
15
19
|
"Transition from #{self.from.name} -> #{self.to.name} with event #{self.event}"
|
16
20
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ExecutableTest < Test::Unit::TestCase
|
4
|
+
context 'Should execute method without arguments' do
|
5
|
+
should 'with symbol' do
|
6
|
+
assert_equal 4, FSM::Executable.new(:length).execute('1234')
|
7
|
+
end
|
8
|
+
should 'with string' do
|
9
|
+
assert_equal 4, FSM::Executable.new('length').execute('1234')
|
10
|
+
end
|
11
|
+
should 'with proc' do
|
12
|
+
assert_equal 4, FSM::Executable.new(lambda { |target| target.length }).execute('1234')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'Should execute method with arguments' do
|
17
|
+
should 'with symbol' do
|
18
|
+
assert_equal 'firstlast', FSM::Executable.new(:+).execute('first', 'last')
|
19
|
+
end
|
20
|
+
should 'with string' do
|
21
|
+
assert_equal 'firstlast', FSM::Executable.new('+').execute('first', 'last')
|
22
|
+
end
|
23
|
+
|
24
|
+
should 'with proc varargs' do
|
25
|
+
assert_equal 'some things are good', FSM::Executable.new(lambda {|target, *args| args.join(' ') }).execute(:foo, 'some', 'things', 'are', 'good')
|
26
|
+
end
|
27
|
+
|
28
|
+
should 'with proc' do
|
29
|
+
assert_equal 'some things', FSM::Executable.new(lambda {|target, a, b| "#{a} #{b}" }).execute(:foo, 'some', 'things')
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Invoice
|
4
|
+
attr_accessor(:state, :amount)
|
5
|
+
include FSM
|
6
|
+
define_fsm do
|
7
|
+
state_attribute(:state)
|
8
|
+
state(:open)
|
9
|
+
state(:paid)
|
10
|
+
state(:refunded)
|
11
|
+
|
12
|
+
transition(:pay, :open, :paid, :event => :event_paid)
|
13
|
+
transition(:refund, :paid, :refunded)
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def initialize(state = :open, amount = 1000)
|
18
|
+
self.state = state
|
19
|
+
self.amount = amount
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def event_paid(amount_paid)
|
24
|
+
raise "Not enough paid" unless amount_paid == self.amount
|
25
|
+
self.amount -= amount_paid
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class InvoiceTest < Test::Unit::TestCase
|
30
|
+
context 'Invoice' do
|
31
|
+
|
32
|
+
should 'Initial State is the first state defined unless no initial() call was made' do
|
33
|
+
invoice = Invoice.new
|
34
|
+
assert_equal(:open, invoice.state)
|
35
|
+
end
|
36
|
+
|
37
|
+
should 'Accept an initial state from outside' do
|
38
|
+
invoice = Invoice.new(:paid)
|
39
|
+
assert_equal(:paid, invoice.state)
|
40
|
+
end
|
41
|
+
|
42
|
+
should 'Trasition to paid and then refunded' do
|
43
|
+
invoice = Invoice.new
|
44
|
+
assert_equal(:open, invoice.state)
|
45
|
+
|
46
|
+
invoice.pay(1000)
|
47
|
+
assert_equal(:paid, invoice.state)
|
48
|
+
|
49
|
+
invoice.refund
|
50
|
+
assert_equal(:refunded, invoice.state)
|
51
|
+
end
|
52
|
+
|
53
|
+
should 'Raise on illegal transition' do
|
54
|
+
invoice = Invoice.new
|
55
|
+
assert_raise(FSM::InvalidStateTransition) do
|
56
|
+
invoice.refund
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
should 'Pass the arguments to the event handler' do
|
61
|
+
invoice = Invoice.new
|
62
|
+
invoice.pay(1000)
|
63
|
+
assert_equal(0, invoice.amount)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class OptionsTest < Test::Unit::TestCase
|
4
|
+
include FSM::Options::InstanceMethods
|
5
|
+
context 'assert_options' do
|
6
|
+
should 'allow empty options' do
|
7
|
+
assert_options({})
|
8
|
+
end
|
9
|
+
should('throw on unknown key') do
|
10
|
+
assert_raise(ArgumentError) do
|
11
|
+
assert_options({:foo => 12}, [], [])
|
12
|
+
end
|
13
|
+
assert_raise(ArgumentError) do
|
14
|
+
assert_options({:foo => 12}, [:optional], [])
|
15
|
+
end
|
16
|
+
assert_raise(ArgumentError) do
|
17
|
+
assert_options({:foo => 12}, [], [:mandatory])
|
18
|
+
end
|
19
|
+
assert_raise(ArgumentError) do
|
20
|
+
assert_options({:foo => 12}, [:optional], [:mandatory])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
should('find missong mandatory options') do
|
24
|
+
assert_raise(ArgumentError) do
|
25
|
+
assert_options({}, [], [:foo])
|
26
|
+
end
|
27
|
+
assert_raise(ArgumentError) do
|
28
|
+
assert_options({:foo => 12}, [], [:bar])
|
29
|
+
end
|
30
|
+
assert_raise(ArgumentError) do
|
31
|
+
assert_options({:foo => 12}, [:bar], [:bar])
|
32
|
+
end
|
33
|
+
assert_options({:foo => 42}, [], [:foo])
|
34
|
+
assert_options({:foo => 42}, [:foo], [:foo])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/test/state_test.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StateTest < Test::Unit::TestCase
|
4
|
+
context 'Initializer' do
|
5
|
+
should 'require name' do
|
6
|
+
assert_raise(ArgumentError) do
|
7
|
+
FSM::State.new(nil, nil, nil)
|
8
|
+
end
|
9
|
+
|
10
|
+
FSM::State.new('bla')
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'allow only valid options' do
|
14
|
+
assert_raise(ArgumentError) do
|
15
|
+
FSM::State.new('bla', :foo => 12)
|
16
|
+
end
|
17
|
+
FSM::State.new('bla', :enter => :some)
|
18
|
+
FSM::State.new('bla', :exit => :some)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TransitionTest < Test::Unit::TestCase
|
4
|
+
context 'Initializer' do
|
5
|
+
should 'require name, from and to' do
|
6
|
+
assert_raise(ArgumentError) do
|
7
|
+
FSM::Transition.new(nil, nil, nil)
|
8
|
+
end
|
9
|
+
assert_raise(ArgumentError) do
|
10
|
+
FSM::Transition.new('bla', nil, nil)
|
11
|
+
end
|
12
|
+
assert_raise(ArgumentError) do
|
13
|
+
FSM::Transition.new(nil, 'bli', nil)
|
14
|
+
end
|
15
|
+
assert_raise(ArgumentError) do
|
16
|
+
FSM::Transition.new(nil, nil, 'blo')
|
17
|
+
end
|
18
|
+
assert_raise(ArgumentError) do
|
19
|
+
FSM::Transition.new('bli', 'bli', nil)
|
20
|
+
end
|
21
|
+
assert_raise(ArgumentError) do
|
22
|
+
FSM::Transition.new(nil, 'blo', 'blo')
|
23
|
+
end
|
24
|
+
assert_raise(ArgumentError) do
|
25
|
+
FSM::Transition.new('blo', nil, 'bli')
|
26
|
+
end
|
27
|
+
|
28
|
+
FSM::Transition.new('bla', 'bli', 'blo')
|
29
|
+
end
|
30
|
+
|
31
|
+
should 'allow only valid options' do
|
32
|
+
assert_raise(ArgumentError) do
|
33
|
+
FSM::Transition.new('bla', 'bli', 'blo', :foo => 12)
|
34
|
+
end
|
35
|
+
FSM::Transition.new('bla', 'bli', 'blo', :event => :some)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simplificator-fsm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- simplificator
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-05-
|
12
|
+
date: 2009-05-18 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -28,14 +28,19 @@ files:
|
|
28
28
|
- Rakefile
|
29
29
|
- VERSION.yml
|
30
30
|
- lib/fsm.rb
|
31
|
+
- lib/fsm/builder.rb
|
31
32
|
- lib/fsm/errors.rb
|
32
33
|
- lib/fsm/executable.rb
|
33
34
|
- lib/fsm/machine.rb
|
34
|
-
- lib/fsm/
|
35
|
+
- lib/fsm/options.rb
|
35
36
|
- lib/fsm/state.rb
|
36
37
|
- lib/fsm/transition.rb
|
37
|
-
- test/
|
38
|
+
- test/executable_test.rb
|
39
|
+
- test/invoice_sample_test.rb
|
40
|
+
- test/options_test.rb
|
41
|
+
- test/state_test.rb
|
38
42
|
- test/test_helper.rb
|
43
|
+
- test/transition_test.rb
|
39
44
|
has_rdoc: true
|
40
45
|
homepage: http://github.com/simplificator/fsm
|
41
46
|
post_install_message:
|
@@ -63,5 +68,9 @@ signing_key:
|
|
63
68
|
specification_version: 3
|
64
69
|
summary: A simple finite state machine (FSM) gem.
|
65
70
|
test_files:
|
66
|
-
- test/
|
71
|
+
- test/executable_test.rb
|
72
|
+
- test/invoice_sample_test.rb
|
73
|
+
- test/options_test.rb
|
74
|
+
- test/state_test.rb
|
67
75
|
- test/test_helper.rb
|
76
|
+
- test/transition_test.rb
|
data/lib/fsm/machine_config.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
module FSM
|
2
|
-
class MachineConfig
|
3
|
-
CONFIG_METHODS = %w[state transition initial attribute]
|
4
|
-
instance_methods.each do |m|
|
5
|
-
undef_method m unless m == '__send__' || m == '__id__' || m == 'instance_eval'
|
6
|
-
end
|
7
|
-
|
8
|
-
def initialize(target)
|
9
|
-
@target = target
|
10
|
-
end
|
11
|
-
|
12
|
-
def process(&block)
|
13
|
-
instance_eval(&block)
|
14
|
-
end
|
15
|
-
|
16
|
-
def method_missing(sym, *args, &block)
|
17
|
-
raise "Unknown config method '#{sym}'. Only #{CONFIG_METHODS.map() {|item| "'#{item}'" }.join(', ')} are known" unless CONFIG_METHODS.include?(sym.to_s)
|
18
|
-
@target.__send__(sym, *args, &block)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|