strict_machine 0.1.4 → 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.
- checksums.yaml +4 -4
- data/NOTES.md +10 -0
- data/lib/strict_machine.rb +2 -1
- data/lib/strict_machine/base.rb +6 -79
- data/lib/strict_machine/definition/state.rb +5 -3
- data/lib/strict_machine/definition_context.rb +5 -17
- data/lib/strict_machine/mount_state_machine.rb +2 -24
- data/lib/strict_machine/mount_state_machine/class_methods.rb +3 -1
- data/lib/strict_machine/mount_state_machine/instance_methods.rb +74 -0
- data/spec/dsl_spec.rb +25 -20
- data/spec/dummy/dummy.rb +2 -0
- data/spec/mountable_machine_spec.rb +12 -15
- metadata +5 -4
- data/lib/strict_machine/mount_state_machine/initializer.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd8176055fcd833b0c5e292f89d251ef32067687
|
4
|
+
data.tar.gz: 8c5526e2c67353ce0bb71d52713584219a901d1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e364a995db6ffbde2984893fdc5f2e0b7d2dccf67b85514904aba17a5e66b86db90d958eb1e8e475e85b63589f70ecdcd7cb7cba6f800fb930073c0e7b043257
|
7
|
+
data.tar.gz: 0c2f0375d4df894541648ca6ddd7e2ebc7a14ca90edf6ed867c91a63b8effe15d914aece8e9496d6f9c964b10541bd9fb3853e145d717655272516eabfcb29ff
|
data/NOTES.md
ADDED
data/lib/strict_machine.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative "ext/object"
|
2
2
|
|
3
3
|
module StrictMachine
|
4
|
-
VERSION = "0.
|
4
|
+
VERSION = "0.2.0".freeze
|
5
5
|
|
6
6
|
class << self; attr_accessor :list; end
|
7
7
|
end
|
@@ -11,4 +11,5 @@ class InvalidTransitionError < StandardError; end
|
|
11
11
|
class TransitionNotFoundError < StandardError; end
|
12
12
|
class GuardedTransitionError < StandardError; end
|
13
13
|
|
14
|
+
require_relative "strict_machine/mount_state_machine"
|
14
15
|
require_relative "strict_machine/base"
|
data/lib/strict_machine/base.rb
CHANGED
@@ -1,93 +1,20 @@
|
|
1
|
-
require_relative "mount_state_machine"
|
2
1
|
require_relative "definition_context"
|
3
2
|
|
4
3
|
module StrictMachine
|
5
4
|
class Base
|
6
|
-
|
5
|
+
include MountStateMachine::InstanceMethods
|
7
6
|
|
8
|
-
def self.strict_machine(&block)
|
7
|
+
def self.strict_machine(state_attr = "state", &block)
|
9
8
|
dc = DefinitionContext.new
|
10
9
|
dc.instance_eval(&block)
|
11
10
|
|
11
|
+
stored = self
|
12
|
+
|
12
13
|
metaclass.instance_eval do
|
13
14
|
define_method(:definition) { dc }
|
15
|
+
define_method(:strict_machine_class) { stored }
|
16
|
+
define_method(:strict_machine_attr) { "@#{state_attr}".to_sym }
|
14
17
|
end
|
15
18
|
end
|
16
|
-
|
17
|
-
attr_writer :state_attr
|
18
|
-
|
19
|
-
def self.states
|
20
|
-
definition.states
|
21
|
-
end
|
22
|
-
|
23
|
-
def boot!
|
24
|
-
@state_attr = :status if @state_attr.nil?
|
25
|
-
|
26
|
-
change_state(self.class.states.first.name)
|
27
|
-
end
|
28
|
-
|
29
|
-
def current_state
|
30
|
-
instance_variable_get "@#{@state_attr}"
|
31
|
-
end
|
32
|
-
|
33
|
-
def definition
|
34
|
-
self.class.definition
|
35
|
-
end
|
36
|
-
|
37
|
-
def transition?(meth, state)
|
38
|
-
definition.transition?(meth, state)
|
39
|
-
end
|
40
|
-
|
41
|
-
def trigger_transition(trigger, stored = self)
|
42
|
-
dt = Time.now
|
43
|
-
|
44
|
-
is_bang = !trigger.to_s.index("!").nil?
|
45
|
-
transition = from_state.get_transition(trigger, is_bang)
|
46
|
-
|
47
|
-
if transition.guarded? && !is_bang
|
48
|
-
raise GuardedTransitionError unless stored.public_send(
|
49
|
-
transition.guard
|
50
|
-
)
|
51
|
-
end
|
52
|
-
|
53
|
-
new_state = definition.get_state_by_name(transition.to)
|
54
|
-
new_state.on_entry.each do |proc|
|
55
|
-
stored.instance_exec(current_state, trigger.to_sym, &proc)
|
56
|
-
end
|
57
|
-
|
58
|
-
duration = Time.now - dt
|
59
|
-
|
60
|
-
definition.transitions.each do |proc|
|
61
|
-
stored.instance_exec(
|
62
|
-
current_state, new_state.name, trigger.to_sym, duration, &proc
|
63
|
-
)
|
64
|
-
end
|
65
|
-
|
66
|
-
change_state(new_state.name)
|
67
|
-
end
|
68
|
-
|
69
|
-
###
|
70
|
-
|
71
|
-
def respond_to?(meth, _include_private = false)
|
72
|
-
transition?(meth, current_state)
|
73
|
-
end
|
74
|
-
|
75
|
-
def method_missing(meth, *_args)
|
76
|
-
if transition?(meth, current_state)
|
77
|
-
trigger_transition(meth, mounted_on || self)
|
78
|
-
else
|
79
|
-
raise TransitionNotFoundError, meth
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
private
|
84
|
-
|
85
|
-
def from_state
|
86
|
-
definition.get_state_by_name(current_state)
|
87
|
-
end
|
88
|
-
|
89
|
-
def change_state(new_state, _is_initial = false)
|
90
|
-
instance_variable_set "@#{@state_attr}".to_sym, new_state
|
91
|
-
end
|
92
19
|
end
|
93
20
|
end
|
@@ -16,11 +16,13 @@ module StrictMachine
|
|
16
16
|
)
|
17
17
|
end
|
18
18
|
|
19
|
-
def get_transition(name
|
20
|
-
name = name
|
19
|
+
def get_transition(name)
|
20
|
+
name = name.to_s
|
21
|
+
is_bang = name.end_with?("!")
|
22
|
+
name = name[0..-2] if is_bang
|
21
23
|
|
22
24
|
@transition_definitions.each do |this_transition|
|
23
|
-
return this_transition if this_transition.name == name.to_sym
|
25
|
+
return is_bang, this_transition if this_transition.name == name.to_sym
|
24
26
|
end
|
25
27
|
|
26
28
|
raise TransitionNotFoundError, name
|
@@ -3,28 +3,12 @@ require_relative "definition/state"
|
|
3
3
|
module StrictMachine
|
4
4
|
class DefinitionContext
|
5
5
|
attr_reader :states, :transitions
|
6
|
-
attr_accessor :mounted_on
|
7
6
|
|
8
7
|
def initialize
|
9
8
|
@states = []
|
10
9
|
@transitions = []
|
11
10
|
end
|
12
11
|
|
13
|
-
def transition?(name, state)
|
14
|
-
is_bang = (name[-1] == "!")
|
15
|
-
name = is_bang ? name[0..-2] : name
|
16
|
-
|
17
|
-
@states.each do |this_state|
|
18
|
-
next unless this_state.name.to_sym == state.to_sym
|
19
|
-
|
20
|
-
this_state.transition_definitions.each do |this_transition|
|
21
|
-
return true if this_transition.name == name.to_sym
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
false
|
26
|
-
end
|
27
|
-
|
28
12
|
def get_state_by_name(name)
|
29
13
|
@states.each do |this_state|
|
30
14
|
return this_state if this_state.name == name
|
@@ -33,7 +17,11 @@ module StrictMachine
|
|
33
17
|
raise StateNotFoundError, name
|
34
18
|
end
|
35
19
|
|
36
|
-
|
20
|
+
def initial_state_name
|
21
|
+
@states.first.name
|
22
|
+
end
|
23
|
+
|
24
|
+
### DSL
|
37
25
|
|
38
26
|
def state(name, &block)
|
39
27
|
@states << State.new(name)
|
@@ -1,33 +1,11 @@
|
|
1
|
-
require_relative "mount_state_machine/
|
1
|
+
require_relative "mount_state_machine/instance_methods"
|
2
2
|
require_relative "mount_state_machine/class_methods"
|
3
3
|
|
4
4
|
module StrictMachine
|
5
5
|
module MountStateMachine
|
6
6
|
def self.included(base)
|
7
7
|
base.extend ClassMethods
|
8
|
-
base.
|
9
|
-
end
|
10
|
-
|
11
|
-
def current_state
|
12
|
-
@state_machine.current_state
|
13
|
-
end
|
14
|
-
|
15
|
-
def state_attr
|
16
|
-
@state_machine.state_attr
|
17
|
-
end
|
18
|
-
|
19
|
-
###
|
20
|
-
|
21
|
-
def method_missing(meth, *args, &block)
|
22
|
-
if @state_machine.transition?(meth, current_state)
|
23
|
-
@state_machine.trigger_transition(meth, self)
|
24
|
-
else
|
25
|
-
super
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def respond_to?(meth, _include_private = false)
|
30
|
-
@state_machine && @state_machine.transition?(meth, current_state)
|
8
|
+
base.include InstanceMethods
|
31
9
|
end
|
32
10
|
end
|
33
11
|
end
|
@@ -2,9 +2,11 @@ module StrictMachine
|
|
2
2
|
module MountStateMachine
|
3
3
|
module ClassMethods
|
4
4
|
def mount_state_machine(klass, options = {})
|
5
|
+
state_attr = options.fetch(:state, :state)
|
6
|
+
|
5
7
|
metaclass.instance_eval do
|
6
8
|
define_method(:strict_machine_class) { klass }
|
7
|
-
define_method(:
|
9
|
+
define_method(:strict_machine_attr) { "@#{state_attr}".to_sym }
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module StrictMachine
|
2
|
+
module MountStateMachine
|
3
|
+
module InstanceMethods
|
4
|
+
def trigger(*transitions)
|
5
|
+
transitions.map {|t| change_state(t, state) }
|
6
|
+
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def state
|
11
|
+
write_initial_state if current_state_attr_value.nil?
|
12
|
+
|
13
|
+
current_state_attr_value
|
14
|
+
end
|
15
|
+
|
16
|
+
def state_attr
|
17
|
+
self.class.strict_machine_attr.to_s.gsub("@",'')
|
18
|
+
end
|
19
|
+
|
20
|
+
def states
|
21
|
+
definition.states
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def current_state_attr_value
|
27
|
+
instance_variable_get state_machine_attr_name
|
28
|
+
end
|
29
|
+
|
30
|
+
def write_initial_state
|
31
|
+
write_state(definition.initial_state_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def write_state(value)
|
35
|
+
instance_variable_set state_machine_attr_name, value
|
36
|
+
end
|
37
|
+
|
38
|
+
def state_machine_attr_name
|
39
|
+
self.class.strict_machine_attr
|
40
|
+
end
|
41
|
+
|
42
|
+
def definition
|
43
|
+
self.class.strict_machine_class.definition
|
44
|
+
end
|
45
|
+
|
46
|
+
def change_state(trigger, current_state_name)
|
47
|
+
dt = Time.now
|
48
|
+
current_state = definition.get_state_by_name(current_state_name)
|
49
|
+
is_bang, transition = current_state.get_transition(trigger)
|
50
|
+
|
51
|
+
if transition.guarded? && !is_bang
|
52
|
+
raise GuardedTransitionError unless self.public_send(
|
53
|
+
transition.guard
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
new_state = definition.get_state_by_name(transition.to)
|
58
|
+
new_state.on_entry.each do |proc|
|
59
|
+
self.instance_exec(current_state_name, trigger.to_sym, &proc)
|
60
|
+
end
|
61
|
+
|
62
|
+
duration = Time.now - dt
|
63
|
+
|
64
|
+
definition.transitions.each do |proc|
|
65
|
+
self.instance_exec(
|
66
|
+
current_state_name, new_state.name, trigger.to_sym, duration, &proc
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
write_state(new_state.name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/spec/dsl_spec.rb
CHANGED
@@ -10,8 +10,9 @@ describe DefinitionContext do
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
expect(
|
13
|
+
machine = klass.new
|
14
|
+
expect(machine.states.size).to eq(1)
|
15
|
+
expect(machine.states.first.name).to eq(:initial)
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
@@ -27,10 +28,9 @@ describe DefinitionContext do
|
|
27
28
|
end
|
28
29
|
|
29
30
|
machine = klass.new
|
30
|
-
machine.
|
31
|
-
|
32
|
-
machine.
|
33
|
-
expect(machine.current_state).to eq(:done)
|
31
|
+
expect(machine.state).to eq(:initial)
|
32
|
+
machine.trigger(:hop)
|
33
|
+
expect(machine.state).to eq(:done)
|
34
34
|
end
|
35
35
|
|
36
36
|
it "honors guard statements" do
|
@@ -48,9 +48,8 @@ describe DefinitionContext do
|
|
48
48
|
end
|
49
49
|
|
50
50
|
machine = klass.new
|
51
|
-
machine.
|
52
|
-
expect
|
53
|
-
expect { machine.hop }.to raise_error(GuardedTransitionError)
|
51
|
+
expect(machine.state).to eq(:initial)
|
52
|
+
expect { machine.trigger(:hop) }.to raise_error(GuardedTransitionError)
|
54
53
|
end
|
55
54
|
|
56
55
|
it "bypasses guard statements with a bang" do
|
@@ -68,9 +67,8 @@ describe DefinitionContext do
|
|
68
67
|
end
|
69
68
|
|
70
69
|
machine = klass.new
|
71
|
-
machine.
|
72
|
-
machine.
|
73
|
-
expect(machine.current_state).to eql(:done)
|
70
|
+
machine.trigger(:hop!)
|
71
|
+
expect(machine.state).to eql(:done)
|
74
72
|
end
|
75
73
|
|
76
74
|
it "raises error on invalid state transitions" do
|
@@ -83,9 +81,8 @@ describe DefinitionContext do
|
|
83
81
|
end
|
84
82
|
|
85
83
|
machine = klass.new
|
86
|
-
machine.
|
87
|
-
expect { machine.
|
88
|
-
expect { machine.zing }.to raise_error(TransitionNotFoundError)
|
84
|
+
expect { machine.trigger(:hop) }.to raise_error(StateNotFoundError)
|
85
|
+
expect { machine.trigger(:zing) }.to raise_error(TransitionNotFoundError)
|
89
86
|
end
|
90
87
|
end
|
91
88
|
|
@@ -115,8 +112,7 @@ describe DefinitionContext do
|
|
115
112
|
expect_any_instance_of(klass).to receive(:log).with(:initial, :hop)
|
116
113
|
expect_any_instance_of(klass).to receive(:log2).with(:initial, :hop)
|
117
114
|
|
118
|
-
machine.
|
119
|
-
machine.hop
|
115
|
+
machine.trigger(:hop)
|
120
116
|
end
|
121
117
|
end
|
122
118
|
|
@@ -147,9 +143,18 @@ describe DefinitionContext do
|
|
147
143
|
:middle, :final, :hop, any_args
|
148
144
|
)
|
149
145
|
|
150
|
-
machine.
|
151
|
-
|
152
|
-
|
146
|
+
machine.trigger(:hop, :hop)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
it "sets state to the given status option name" do
|
151
|
+
klass = Class.new(StrictMachine::Base) do
|
152
|
+
strict_machine("meh") do
|
153
|
+
state :initial
|
154
|
+
end
|
153
155
|
end
|
156
|
+
|
157
|
+
machine = klass.new
|
158
|
+
expect(machine.state_attr).to eq("meh")
|
154
159
|
end
|
155
160
|
end
|
data/spec/dummy/dummy.rb
CHANGED
@@ -2,46 +2,43 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe "mounted StrictMachine" do
|
4
4
|
context "state shifting" do
|
5
|
-
require_relative "dummy/dummy_state_machine"
|
6
5
|
require_relative "dummy/dummy"
|
7
6
|
|
8
7
|
let!(:dummy) { Dummy.new(2) }
|
9
8
|
|
10
|
-
it "
|
11
|
-
expect(dummy.
|
9
|
+
it "enumerates states" do
|
10
|
+
expect(dummy.states.size).to eq(5)
|
11
|
+
expect(dummy.states.first.name).to eq(:new)
|
12
12
|
end
|
13
13
|
|
14
14
|
it "has an initial state" do
|
15
|
-
expect(dummy.
|
15
|
+
expect(dummy.state).to eq(:new)
|
16
16
|
end
|
17
17
|
|
18
18
|
it "shifts state" do
|
19
|
-
dummy.submit
|
19
|
+
dummy.trigger(:submit)
|
20
20
|
|
21
|
-
expect(dummy.
|
21
|
+
expect(dummy.state).to eq(:awaiting_review)
|
22
22
|
end
|
23
23
|
|
24
24
|
it "honors guard statements" do
|
25
|
-
dummy.submit
|
26
|
-
dummy.review
|
25
|
+
dummy.trigger(:submit, :review)
|
27
26
|
|
28
|
-
expect { dummy.reject }.to raise_error(GuardedTransitionError)
|
27
|
+
expect { dummy.trigger(:reject) }.to raise_error(GuardedTransitionError)
|
29
28
|
|
30
|
-
expect(dummy.
|
29
|
+
expect(dummy.state).to eq(:under_review)
|
31
30
|
end
|
32
31
|
|
33
32
|
it "bypasses guard statements with bangs" do
|
34
|
-
dummy.submit
|
35
|
-
dummy.review
|
36
|
-
dummy.reject!
|
33
|
+
dummy.trigger(:submit, :review, :reject!)
|
37
34
|
|
38
|
-
expect(dummy.
|
35
|
+
expect(dummy.state).to eq(:rejected)
|
39
36
|
end
|
40
37
|
|
41
38
|
it "calls on_transition blocks" do
|
42
39
|
expect(dummy).to receive(:log)
|
43
40
|
|
44
|
-
dummy.submit
|
41
|
+
dummy.trigger(:submit)
|
45
42
|
end
|
46
43
|
|
47
44
|
it "sets state to the given status option name" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strict_machine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bruno Antunes
|
@@ -81,6 +81,7 @@ files:
|
|
81
81
|
- ".travis.yml"
|
82
82
|
- Gemfile
|
83
83
|
- LICENSE.txt
|
84
|
+
- NOTES.md
|
84
85
|
- README.md
|
85
86
|
- Rakefile
|
86
87
|
- bin/console
|
@@ -93,7 +94,7 @@ files:
|
|
93
94
|
- lib/strict_machine/definition_context.rb
|
94
95
|
- lib/strict_machine/mount_state_machine.rb
|
95
96
|
- lib/strict_machine/mount_state_machine/class_methods.rb
|
96
|
-
- lib/strict_machine/mount_state_machine/
|
97
|
+
- lib/strict_machine/mount_state_machine/instance_methods.rb
|
97
98
|
- spec/dsl_spec.rb
|
98
99
|
- spec/dummy/dummy.rb
|
99
100
|
- spec/dummy/dummy_state_machine.rb
|
@@ -126,7 +127,7 @@ specification_version: 4
|
|
126
127
|
summary: State machine functionality for Ruby classes
|
127
128
|
test_files:
|
128
129
|
- spec/dsl_spec.rb
|
129
|
-
- spec/dummy/dummy.rb
|
130
|
-
- spec/dummy/dummy_state_machine.rb
|
131
130
|
- spec/mountable_machine_spec.rb
|
132
131
|
- spec/spec_helper.rb
|
132
|
+
- spec/dummy/dummy.rb
|
133
|
+
- spec/dummy/dummy_state_machine.rb
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module StrictMachine
|
2
|
-
module MountStateMachine
|
3
|
-
module Initializer
|
4
|
-
def initialize(*args)
|
5
|
-
if self.class.respond_to?(:strict_machine_class)
|
6
|
-
@state_machine = self.class.strict_machine_class.new
|
7
|
-
@state_machine.mounted_on = self
|
8
|
-
|
9
|
-
options = self.class.strict_machine_options
|
10
|
-
state_attr = options.fetch(:state, :status)
|
11
|
-
@state_machine.state_attr = state_attr
|
12
|
-
|
13
|
-
@state_machine.boot!
|
14
|
-
end
|
15
|
-
|
16
|
-
super
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|