end_state 0.12.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/Gemfile +1 -0
- data/README.md +9 -11
- data/end_state.gemspec +1 -0
- data/lib/end_state/concluder.rb +0 -1
- data/lib/end_state/graph.rb +16 -12
- data/lib/end_state/no_graph.rb +12 -0
- data/lib/end_state/state_machine.rb +55 -140
- data/lib/end_state/state_machine_configuration.rb +77 -0
- data/lib/end_state/transition.rb +35 -49
- data/lib/end_state/transition_configuration.rb +49 -0
- data/lib/end_state/transition_configuration_set.rb +83 -0
- data/lib/end_state/version.rb +1 -1
- data/lib/end_state.rb +4 -1
- data/lib/end_state_matchers.rb +52 -39
- data/spec/end_state/concluder_spec.rb +9 -3
- data/spec/end_state/graph_spec.rb +35 -0
- data/spec/end_state/guard_spec.rb +29 -3
- data/spec/end_state/state_machine_configuration_spec.rb +183 -0
- data/spec/end_state/state_machine_spec.rb +8 -192
- data/spec/end_state/transition_configuration_set_spec.rb +217 -0
- data/spec/end_state/transition_configuration_spec.rb +65 -0
- data/spec/end_state/transition_spec.rb +83 -96
- data/spec/end_state_matchers_spec.rb +105 -0
- data/spec/spec_helper.rb +17 -0
- metadata +31 -8
- data/lib/end_state/state_mapping.rb +0 -25
- data/spec/end_state/state_mapping_spec.rb +0 -94
- data/spec/end_state_spec.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4431610a8257edd10bd8ba72d453f7f6f318c19b
|
4
|
+
data.tar.gz: 3ed496c73b2935568bc12571c0c7d1982446d92f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bdea6ed04d7c9e582264e60ed287c5d7fca801f58e1a81732aaca61e80f0ec50d773cd8a54feb3aa582f5562be6b5ed9be2615ac2faa6765d8939ac9a6dc7b1f
|
7
|
+
data.tar.gz: 1157ea3e2e91b2406fa9377b2ed44d86bb35015ae75e20bc405aeb58145267d9d72806e8653f4fbd5f26bf4fb8245f7dda44313e4166caf542b5963a2d769f11
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/Originate/end_state.svg?branch=master)](https://travis-ci.org/Originate/end_state)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/Originate/end_state/badges/gpa.svg)](https://codeclimate.com/github/Originate/end_state)
|
3
|
+
[![Coverage Status](https://coveralls.io/repos/Originate/end_state/badge.png)](https://coveralls.io/r/Originate/end_state)
|
4
|
+
|
1
5
|
# EndState
|
2
6
|
|
3
7
|
EndState is an unobtrusive way to add state machines to your application.
|
@@ -32,8 +36,8 @@ Transitions can be named by adding an `:as` option.
|
|
32
36
|
class Machine < EndState::StateMachine
|
33
37
|
transition parked: :idling, as: :start
|
34
38
|
transition idling: :first_gear, first_gear: :second_gear, second_gear: :third_gear, as: :shift_up
|
35
|
-
transition third_gear: :second_gear, second_gear: :first_gear, as: shift_down
|
36
|
-
transition first_gear: :idling, as: idle
|
39
|
+
transition third_gear: :second_gear, second_gear: :first_gear, as: :shift_down
|
40
|
+
transition first_gear: :idling, as: :idle
|
37
41
|
transition [:idling, :first_gear] => :parked, as: :park
|
38
42
|
end
|
39
43
|
```
|
@@ -239,17 +243,12 @@ end
|
|
239
243
|
## Events
|
240
244
|
|
241
245
|
By using the `as` option in a transition definition you are creating an event representing that transition.
|
242
|
-
This can allow you to exercise the machine in a more natural "verb" style interaction.
|
243
|
-
|
244
|
-
machine is not in the initial state of the event, the message is added to the `failure_messages`
|
245
|
-
array on the machine. Events, like `transition` have both a standard and a bang (`!`) style. The bang style
|
246
|
-
will raise an exception if there is a problem.
|
246
|
+
This can allow you to exercise the machine in a more natural "verb" style interaction. Events, like `transition`
|
247
|
+
have both a standard and a bang (`!`) style. The bang style will raise an exception if there is a problem.
|
247
248
|
|
248
249
|
```ruby
|
249
250
|
class Machine < EndState::StateMachine
|
250
|
-
transition a: :b, as: :go
|
251
|
-
t.blocked 'Cannot go!'
|
252
|
-
end
|
251
|
+
transition a: :b, as: :go
|
253
252
|
end
|
254
253
|
|
255
254
|
machine = Machine.new(StatefulObject.new(:a))
|
@@ -257,7 +256,6 @@ machine = Machine.new(StatefulObject.new(:a))
|
|
257
256
|
machine.go # => true
|
258
257
|
machine.state # => :b
|
259
258
|
machine.go # => false
|
260
|
-
machine.failure_messages # => ['Cannot go!']
|
261
259
|
machine.go! # => raises InvalidTransition
|
262
260
|
```
|
263
261
|
|
data/end_state.gemspec
CHANGED
data/lib/end_state/concluder.rb
CHANGED
data/lib/end_state/graph.rb
CHANGED
@@ -10,19 +10,23 @@ module EndState
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def draw
|
13
|
-
|
14
|
-
left, right = t.to_a.flatten
|
15
|
-
nodes[left] ||= add_nodes(left.to_s)
|
16
|
-
nodes[right] ||= add_nodes(right.to_s)
|
17
|
-
edge = add_edges nodes[left], nodes[right]
|
18
|
-
if event_labels
|
19
|
-
event = machine.events.detect do |event, transition|
|
20
|
-
transition.include? t
|
21
|
-
end
|
22
|
-
edge[:label] = event.first.to_s if event
|
23
|
-
end
|
24
|
-
end
|
13
|
+
add_transitions
|
25
14
|
self
|
26
15
|
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def add_transitions
|
20
|
+
machine.transition_configurations.each do |start_state, end_state, _, event|
|
21
|
+
add_transition(start_state, end_state, event)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_transition start_state, end_state, event
|
26
|
+
nodes[start_state] ||= add_node(start_state.to_s)
|
27
|
+
nodes[end_state] ||= add_node(end_state.to_s)
|
28
|
+
edge = add_edge nodes[start_state], nodes[end_state]
|
29
|
+
edge[:label] = event.to_s if event && event_labels
|
30
|
+
end
|
27
31
|
end
|
28
32
|
end
|
@@ -1,139 +1,78 @@
|
|
1
1
|
module EndState
|
2
2
|
class StateMachine < SimpleDelegator
|
3
|
+
extend StateMachineConfiguration
|
4
|
+
|
3
5
|
attr_accessor :failure_messages, :success_messages
|
4
6
|
|
5
7
|
def initialize(object)
|
6
8
|
super
|
7
|
-
Action.new(self, self.class.initial_state).call if
|
8
|
-
end
|
9
|
-
|
10
|
-
@initial_state = :__nil__
|
11
|
-
@mode = :soft
|
12
|
-
|
13
|
-
def self.initial_state
|
14
|
-
@initial_state
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.set_initial_state(state)
|
18
|
-
@initial_state = state.to_sym
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.treat_all_transitions_as_hard!
|
22
|
-
@mode = :hard
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.mode
|
26
|
-
@mode
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.store_states_as_strings!
|
30
|
-
@store_states_as_strings = true
|
9
|
+
Action.new(self, self.class.initial_state).call if state.nil?
|
31
10
|
end
|
32
11
|
|
33
|
-
def
|
34
|
-
|
12
|
+
def object
|
13
|
+
__getobj__
|
35
14
|
end
|
36
15
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
state_map.each do |start_states, end_state|
|
42
|
-
transition = Transition.new(end_state)
|
43
|
-
|
44
|
-
Array(start_states).each do |start_state|
|
45
|
-
state_mapping = StateMapping[start_state.to_sym => end_state.to_sym]
|
46
|
-
transitions[state_mapping] = transition
|
47
|
-
__sm_add_event(transition_alias, state_mapping) unless transition_alias.nil?
|
48
|
-
end
|
49
|
-
|
50
|
-
yield transition if block_given?
|
51
|
-
end
|
16
|
+
def can_transition?(end_state, params = {})
|
17
|
+
return false unless __sm_transition_configuration_for(state, end_state)
|
18
|
+
__sm_transition_for(end_state).will_allow?(params)
|
52
19
|
end
|
53
20
|
|
54
|
-
def self.
|
55
|
-
|
21
|
+
def transition(end_state, params = {}, mode = self.class.mode)
|
22
|
+
__sm_reset_messages
|
23
|
+
return __sm_block_transistion(end_state, mode) unless __sm_transition_configuration_for(state, end_state)
|
24
|
+
__sm_transition_for(end_state, mode).call(params)
|
56
25
|
end
|
57
26
|
|
58
|
-
def
|
59
|
-
|
27
|
+
def transition!(end_state, params = {})
|
28
|
+
transition end_state, params, :hard
|
60
29
|
end
|
61
30
|
|
62
|
-
def
|
63
|
-
|
64
|
-
|
31
|
+
def method_missing(method, *args, &block)
|
32
|
+
return super unless __sm_is_method?(method)
|
33
|
+
return __sm_predicate(method) if __sm_is_state_predicate?(method)
|
34
|
+
__sm_event_transition __sm_event(method), args[0] || {}, __sm_event_mode(method)
|
65
35
|
end
|
66
36
|
|
67
|
-
|
68
|
-
(start_states + end_states).uniq
|
69
|
-
end
|
37
|
+
private
|
70
38
|
|
71
|
-
def
|
72
|
-
|
39
|
+
def __sm_is_method?(method)
|
40
|
+
__sm_is_state_predicate?(method) || __sm_is_event?(method)
|
73
41
|
end
|
74
42
|
|
75
|
-
def
|
76
|
-
|
43
|
+
def __sm_predicate(method)
|
44
|
+
__sm_current_state? __sm_extract_state(method)
|
77
45
|
end
|
78
46
|
|
79
|
-
def
|
80
|
-
|
47
|
+
def __sm_extract_state(method)
|
48
|
+
method.to_s[0..-2].to_sym
|
81
49
|
end
|
82
50
|
|
83
|
-
def
|
84
|
-
|
85
|
-
state = state.to_sym
|
86
|
-
transition = __sm_transition_for(previous_state, state)
|
87
|
-
return __sm_block_transistion(transition, state, :soft) unless transition
|
88
|
-
transition.will_allow? state, params
|
89
|
-
end
|
90
|
-
|
91
|
-
def transition(state, params = {}, mode = self.class.mode)
|
92
|
-
@failure_messages = []
|
93
|
-
@success_messages = []
|
94
|
-
previous_state = self.state ? self.state.to_sym : self.state
|
95
|
-
state = state.to_sym
|
96
|
-
transition = __sm_transition_for(previous_state, state)
|
97
|
-
mode = __sm_actual_mode(mode)
|
98
|
-
return __sm_block_transistion(transition, state, mode) unless transition
|
99
|
-
return __sm_guard_failed(state, mode) unless transition.allowed?(self, params)
|
100
|
-
return false unless transition.action.new(self, state).call
|
101
|
-
return __sm_conclude_failed(state, mode) unless transition.conclude(self, previous_state, params)
|
102
|
-
true
|
51
|
+
def __sm_event(method)
|
52
|
+
method.to_s.gsub('!','').to_sym
|
103
53
|
end
|
104
54
|
|
105
|
-
def
|
106
|
-
|
55
|
+
def __sm_event_mode(method)
|
56
|
+
return :hard if method.to_s.end_with?('!')
|
57
|
+
__sm_actual_mode(:soft)
|
107
58
|
end
|
108
59
|
|
109
|
-
def
|
110
|
-
|
111
|
-
return __sm_current_state?(method) if __sm_state_predicate(method)
|
112
|
-
new_state, mode = __sm_event(method)
|
113
|
-
return false if new_state == :__invalid_event__
|
114
|
-
transition new_state, (args[0] || {}), mode
|
60
|
+
def __sm_is_state_predicate?(method)
|
61
|
+
method.to_s.end_with?('?') && self.class.states.include?(__sm_extract_state(method))
|
115
62
|
end
|
116
63
|
|
117
|
-
|
118
|
-
|
119
|
-
def __sm_predicate_or_event?(method)
|
120
|
-
__sm_state_predicate(method) ||
|
121
|
-
__sm_event(method)
|
64
|
+
def __sm_is_event?(method)
|
65
|
+
self.class.events.include? __sm_event(method)
|
122
66
|
end
|
123
67
|
|
124
|
-
def
|
125
|
-
state
|
126
|
-
return unless self.class.states.include?(state) && method.to_s.end_with?('?')
|
127
|
-
state
|
68
|
+
def __sm_current_state?(end_state)
|
69
|
+
state.to_sym == end_state
|
128
70
|
end
|
129
71
|
|
130
|
-
def
|
131
|
-
|
132
|
-
return
|
133
|
-
|
134
|
-
event = __sm_state_for_event(method.to_s[0..-2].to_sym, :hard)
|
135
|
-
return event, :hard if event
|
136
|
-
nil
|
72
|
+
def __sm_event_transition(event, params, mode)
|
73
|
+
end_state = __sm_state_for_event(event, mode)
|
74
|
+
return false if end_state == :__invalid_event__
|
75
|
+
transition end_state, params, mode
|
137
76
|
end
|
138
77
|
|
139
78
|
def __sm_actual_mode(mode)
|
@@ -141,32 +80,28 @@ module EndState
|
|
141
80
|
mode
|
142
81
|
end
|
143
82
|
|
144
|
-
def __sm_current_state?(method)
|
145
|
-
state.to_sym == __sm_state_predicate(method)
|
146
|
-
end
|
147
|
-
|
148
83
|
def __sm_state_for_event(event, mode)
|
149
|
-
|
150
|
-
return false unless state_mappings
|
151
|
-
state_mappings.each do |state_mapping|
|
152
|
-
return state_mapping.end_state if state_mapping.matches_start_state?(state.to_sym)
|
153
|
-
end
|
154
|
-
return __sm_invalid_event(event, mode)
|
84
|
+
self.class.transition_configurations.get_end_state(state.to_sym, event) || __sm_invalid_event(event, mode)
|
155
85
|
end
|
156
86
|
|
157
87
|
def __sm_invalid_event(event, mode)
|
158
88
|
fail InvalidTransition, "Transition by event: #{event} is invalid." if mode == :hard
|
159
|
-
message = self.class.transitions[self.class.events[event].first].blocked_event_message
|
160
|
-
@failure_messages = [message] if message
|
161
89
|
:__invalid_event__
|
162
90
|
end
|
163
91
|
|
164
|
-
def
|
165
|
-
self.class.
|
166
|
-
self.class.transitions[{ any_state: to }]
|
92
|
+
def __sm_transition_configuration_for(start_state, end_state)
|
93
|
+
self.class.transition_configurations.get_configuration(start_state, end_state)
|
167
94
|
end
|
168
95
|
|
169
|
-
def
|
96
|
+
def __sm_transition_for(end_state, mode = self.class.mode)
|
97
|
+
start_state = state.to_sym
|
98
|
+
end_state = end_state.to_sym
|
99
|
+
configuration = __sm_transition_configuration_for(start_state, end_state)
|
100
|
+
mode = __sm_actual_mode(mode)
|
101
|
+
Transition.new(self, start_state, end_state, configuration, mode)
|
102
|
+
end
|
103
|
+
|
104
|
+
def __sm_block_transistion(state, mode)
|
170
105
|
if self.class.end_states.include? state
|
171
106
|
fail InvalidTransition, "The transition: #{object.state} => #{state} is invalid." if mode == :hard
|
172
107
|
return false
|
@@ -174,29 +109,9 @@ module EndState
|
|
174
109
|
fail UnknownState, "The state: #{state} is unknown."
|
175
110
|
end
|
176
111
|
|
177
|
-
def
|
178
|
-
|
179
|
-
|
180
|
-
end
|
181
|
-
|
182
|
-
def __sm_conclude_failed(state, mode)
|
183
|
-
return false unless mode == :hard
|
184
|
-
fail ConcluderFailed, "The transition to #{state} was rolled back: #{failure_messages.join(', ')}"
|
185
|
-
end
|
186
|
-
|
187
|
-
def self.__sm_add_event(event, state_mapping)
|
188
|
-
events[event] ||= []
|
189
|
-
conflicting_mapping = events[event].find{ |sm| sm.conflicts?(state_mapping) }
|
190
|
-
if conflicting_mapping
|
191
|
-
message =
|
192
|
-
"Attempting to define :#{event} as transitioning from " \
|
193
|
-
":#{state_mapping.start_state} => :#{state_mapping.end_state} when " \
|
194
|
-
":#{conflicting_mapping.start_state} => :#{conflicting_mapping.end_state} already exists. " \
|
195
|
-
"You cannot define multiple transitions from a single state with the same event name."
|
196
|
-
|
197
|
-
fail EventConflict, message
|
198
|
-
end
|
199
|
-
events[event] << state_mapping
|
112
|
+
def __sm_reset_messages
|
113
|
+
@failure_messages = []
|
114
|
+
@success_messages = []
|
200
115
|
end
|
201
116
|
end
|
202
117
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module EndState
|
2
|
+
module StateMachineConfiguration
|
3
|
+
@initial_state = :__nil__
|
4
|
+
@mode = :soft
|
5
|
+
|
6
|
+
def initial_state
|
7
|
+
@initial_state
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_initial_state(state)
|
11
|
+
@initial_state = state.to_sym
|
12
|
+
end
|
13
|
+
|
14
|
+
def treat_all_transitions_as_hard!
|
15
|
+
@mode = :hard
|
16
|
+
end
|
17
|
+
|
18
|
+
def mode
|
19
|
+
@mode
|
20
|
+
end
|
21
|
+
|
22
|
+
def store_states_as_strings!
|
23
|
+
@store_states_as_strings = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def store_states_as_strings
|
27
|
+
!!@store_states_as_strings
|
28
|
+
end
|
29
|
+
|
30
|
+
def transition(state_map)
|
31
|
+
event = state_map.delete(:as)
|
32
|
+
event = event.to_sym unless event.nil?
|
33
|
+
|
34
|
+
configuration = TransitionConfiguration.new
|
35
|
+
yield configuration if block_given?
|
36
|
+
|
37
|
+
state_map.each do |start_states, end_state|
|
38
|
+
Array(start_states).each do |start_state|
|
39
|
+
prevent_event_conflicts(start_state, event)
|
40
|
+
transition_configurations.add(start_state, end_state, configuration, event)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def transition_configurations
|
46
|
+
@transition_configurations ||= TransitionConfigurationSet.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def state_attribute(attribute)
|
50
|
+
define_method(:state) { send(attribute.to_sym) }
|
51
|
+
define_method(:state=) { |val| send("#{attribute}=".to_sym, val) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def events
|
55
|
+
transition_configurations.events
|
56
|
+
end
|
57
|
+
|
58
|
+
def states
|
59
|
+
(start_states + end_states).uniq
|
60
|
+
end
|
61
|
+
|
62
|
+
def start_states
|
63
|
+
transition_configurations.start_states
|
64
|
+
end
|
65
|
+
|
66
|
+
def end_states
|
67
|
+
transition_configurations.end_states
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def prevent_event_conflicts(start_state, event)
|
73
|
+
return unless transition_configurations.event_conflicts?(start_state, event)
|
74
|
+
fail EventConflict, "Attempting to define event '#{event}' on state '#{start_state}', but it is already defined. (Check duplicates and use of 'any_state')"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/end_state/transition.rb
CHANGED
@@ -1,82 +1,68 @@
|
|
1
1
|
module EndState
|
2
2
|
class Transition
|
3
|
-
attr_reader :
|
4
|
-
attr_accessor :action, :guards, :concluders, :allowed_params, :required_params
|
3
|
+
attr_reader :configuration, :mode, :object, :previous_state, :state
|
5
4
|
|
6
|
-
def initialize(state)
|
5
|
+
def initialize(object, previous_state, state, configuration, mode)
|
6
|
+
@object = object
|
7
|
+
@previous_state = previous_state
|
7
8
|
@state = state
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@concluders = []
|
11
|
-
@allowed_params = []
|
12
|
-
@required_params = []
|
9
|
+
@configuration = configuration
|
10
|
+
@mode = mode
|
13
11
|
end
|
14
12
|
|
15
|
-
def
|
13
|
+
def call(params={})
|
14
|
+
return guard_failed unless allowed?(params)
|
15
|
+
return false unless action.new(object, state).call
|
16
|
+
return conclude_failed unless conclude(params)
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def allowed?(params={})
|
16
21
|
raise "Missing params: #{missing_params(params).join(',')}" unless missing_params(params).empty?
|
17
22
|
guards.all? { |guard| guard.new(object, state, params).allowed? }
|
18
23
|
end
|
19
24
|
|
20
|
-
def will_allow?(
|
25
|
+
def will_allow?(params={})
|
21
26
|
return false unless missing_params(params).empty?
|
22
27
|
guards.all? { |guard| guard.new(object, state, params).will_allow? }
|
23
28
|
end
|
24
29
|
|
25
|
-
|
26
|
-
concluders.each_with_object([]) do |concluder, concluded|
|
27
|
-
concluded << concluder
|
28
|
-
return rollback(concluded, object, previous_state, params) unless run_concluder(concluder, object, state, params)
|
29
|
-
end
|
30
|
-
true
|
31
|
-
end
|
32
|
-
|
33
|
-
def custom_action(action)
|
34
|
-
@action = action
|
35
|
-
end
|
30
|
+
private
|
36
31
|
|
37
|
-
def
|
38
|
-
|
32
|
+
def failed(error, message)
|
33
|
+
return false unless mode == :hard
|
34
|
+
fail error, "The transition to #{state} was #{message}: #{object.failure_messages.join(', ')}"
|
39
35
|
end
|
40
36
|
|
41
|
-
def
|
42
|
-
|
37
|
+
def guard_failed
|
38
|
+
failed GuardFailed, 'blocked'
|
43
39
|
end
|
44
40
|
|
45
|
-
def
|
46
|
-
|
41
|
+
def conclude_failed
|
42
|
+
failed ConcluderFailed, 'rolled back'
|
47
43
|
end
|
48
44
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
def require_params(*params)
|
56
|
-
Array(params).flatten.each do |param|
|
57
|
-
self.allowed_params << param unless self.allowed_params.include? param
|
58
|
-
self.required_params << param unless self.required_params.include? param
|
45
|
+
def conclude(params={})
|
46
|
+
concluders.each_with_object([]) do |concluder, concluded|
|
47
|
+
concluded << concluder
|
48
|
+
return rollback(concluded, params) unless concluder.new(object, state, params).call
|
59
49
|
end
|
50
|
+
true
|
60
51
|
end
|
61
52
|
|
62
|
-
def
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
def rollback(concluded, object, previous_state, params)
|
53
|
+
def rollback(concluded, params)
|
54
|
+
concluded.reverse_each { |concluder| concluder.new(object, state, params).rollback }
|
69
55
|
action.new(object, previous_state).rollback
|
70
|
-
concluded.reverse.each { |concluder| concluder.new(object, state, params).rollback }
|
71
56
|
false
|
72
57
|
end
|
73
58
|
|
74
|
-
def run_concluder(concluder, object, state, params)
|
75
|
-
concluder.new(object, state, params).call
|
76
|
-
end
|
77
|
-
|
78
59
|
def missing_params(params)
|
79
60
|
required_params.select { |key| params[key].nil? }
|
80
61
|
end
|
62
|
+
|
63
|
+
[:action, :concluders, :guards, :required_params].each do |method|
|
64
|
+
define_method(method) { configuration.public_send(method) }
|
65
|
+
private method
|
66
|
+
end
|
81
67
|
end
|
82
68
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module EndState
|
2
|
+
class TransitionConfiguration
|
3
|
+
attr_reader :action, :allowed_params, :concluders, :guards, :required_params
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@action = Action
|
7
|
+
@allowed_params = []
|
8
|
+
@concluders = []
|
9
|
+
@guards = []
|
10
|
+
@required_params = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def custom_action(action)
|
14
|
+
@action = action
|
15
|
+
end
|
16
|
+
|
17
|
+
def guard(*guards)
|
18
|
+
Array(guards).flatten.each { |guard| self.guards << guard }
|
19
|
+
end
|
20
|
+
|
21
|
+
def concluder(*concluders)
|
22
|
+
Array(concluders).flatten.each { |concluder| self.concluders << concluder }
|
23
|
+
end
|
24
|
+
|
25
|
+
def persistence_on
|
26
|
+
concluder Concluders::Persistence
|
27
|
+
end
|
28
|
+
|
29
|
+
def allow_params(*params)
|
30
|
+
Array(params).flatten.each do |param|
|
31
|
+
append_unless_included(:allowed_params, param)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def require_params(*params)
|
36
|
+
Array(params).flatten.each do |param|
|
37
|
+
append_unless_included(:allowed_params, param)
|
38
|
+
append_unless_included(:required_params, param)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def append_unless_included(name, value)
|
45
|
+
attribute = self.send(name)
|
46
|
+
attribute << value unless attribute.include? value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|