simplificator-fsm 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|