end_state 0.12.0 → 1.0.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/.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
|
+
[](https://travis-ci.org/Originate/end_state)
|
2
|
+
[](https://codeclimate.com/github/Originate/end_state)
|
3
|
+
[](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
|