end_state 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/.hound.yml +17 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +202 -0
- data/Rakefile +6 -0
- data/end_state.gemspec +25 -0
- data/examples/example1.rb +70 -0
- data/lib/end_state/action.rb +19 -0
- data/lib/end_state/errors.rb +7 -0
- data/lib/end_state/finalizer.rb +19 -0
- data/lib/end_state/finalizers/persistence.rb +15 -0
- data/lib/end_state/finalizers.rb +1 -0
- data/lib/end_state/guard.rb +28 -0
- data/lib/end_state/state_machine.rb +93 -0
- data/lib/end_state/transition.rb +57 -0
- data/lib/end_state/version.rb +3 -0
- data/lib/end_state.rb +12 -0
- data/spec/end_state/action_spec.rb +23 -0
- data/spec/end_state/finalizers/persistence_spec.rb +42 -0
- data/spec/end_state/state_machine_spec.rb +327 -0
- data/spec/end_state/transition_spec.rb +120 -0
- data/spec/end_state_spec.rb +4 -0
- data/spec/spec_helper.rb +2 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fd9f6c8fbf5f8ec9fd075dabbd2011aa51420465
|
4
|
+
data.tar.gz: 12ba88eafad32a520b119756773f723482054536
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 569f287932d9c85bb81ff72bfba03e8c5d78d1ea03d492a2ba8379213f794e5fe12f445ee13eab17036c775789a9a88c4525256b71738ca07601c48850c6ea9d
|
7
|
+
data.tar.gz: 497920437e5eea536f63249ffe9bf84875793ab2e39c276cbf1d56e627953a90a05967500acb503f03802b3ad115d5bc4d680f7e528e51052a89c9eb6cd21681
|
data/.gitignore
ADDED
data/.hound.yml
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 alexpeachey
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
# EndState
|
2
|
+
|
3
|
+
EndState is an unobtrusive way to add state machines to your application.
|
4
|
+
|
5
|
+
An `EndState::StateMachine` acts as a decorator of sorts for your stateful object.
|
6
|
+
Your stateful object does not need to know it is being used in a state machine and
|
7
|
+
only needs to respond to `state` and `state=`. (This is customizable)
|
8
|
+
|
9
|
+
The control flow for guarding against transitions and performing post-transition
|
10
|
+
operations is handled by classes you create allowing maximum separation of responsibilities.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
gem 'end_state'
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install end_state
|
25
|
+
|
26
|
+
## StateMachine
|
27
|
+
|
28
|
+
Create a state machine by subclassing `EndState::StateMachine`.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
class Machine < EndState::StateMachine
|
32
|
+
transition a: :b
|
33
|
+
transition b: :c
|
34
|
+
transition [:b, :c] => :a
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
Use it by wrapping a stateful object.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class StatefulObject
|
42
|
+
attr_accessor :state
|
43
|
+
|
44
|
+
def initialize(state)
|
45
|
+
@state = state
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
machine = Machine.new(StatefulObject.new(:a))
|
50
|
+
|
51
|
+
machine.transition :b # => true
|
52
|
+
machine.state # => :b
|
53
|
+
machine.b? # => true
|
54
|
+
machine.c! # => true
|
55
|
+
machine.state # => :c
|
56
|
+
machine.can_transition? :b # => false
|
57
|
+
machine.can_transition? :a # => true
|
58
|
+
machine.b! # => false
|
59
|
+
machine.a! # => true
|
60
|
+
machine.state # => :a
|
61
|
+
```
|
62
|
+
|
63
|
+
## Guards
|
64
|
+
|
65
|
+
Guards can be created by subclassing `EndState::Guard`. Your class will be provided access to:
|
66
|
+
|
67
|
+
* `object` - The wrapped object.
|
68
|
+
* `state` - The desired state.
|
69
|
+
* `params` - A hash of params as set in the transition definition.
|
70
|
+
|
71
|
+
Your class should implement the `will_allow?` method which must return true or false.
|
72
|
+
|
73
|
+
Optionally you can implement the `passed` and/or `failed` methods which will be called after the guard passes or fails.
|
74
|
+
These will only be called during the check performed during the transition and will not be fired when asking `can_transition?`.
|
75
|
+
These hooks can be useful for things like logging.
|
76
|
+
|
77
|
+
The wrapped object has an array `failure_messages` available for tracking reasons for invalid transitions. You may shovel
|
78
|
+
a reason (string) into this if you want to provide information on why your guard failed.
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
class EasyGuard < EndState::Guard
|
82
|
+
def will_allow?
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
def failed
|
87
|
+
Rails.logger.error "Failed to transition to state #{state} from #{object.state}."
|
88
|
+
end
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
A guard can be added to the transition definition:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
class Machine < EndState::StateMachine
|
96
|
+
transition a: :b do |t|
|
97
|
+
t.guard EasyGuard
|
98
|
+
t.guard SomeOtherGuard, option1: 'Some Option', option2: 'Some Other Option'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
## Finalizers
|
104
|
+
|
105
|
+
Finalizers can be created by subclassing `EndState::Finalizer`. Your class will be provided access to:
|
106
|
+
|
107
|
+
* `object` - The wrapped object that has been transitioned.
|
108
|
+
* `state` - The previous state.
|
109
|
+
* `params` - A hash of params as set in the transition definition.
|
110
|
+
|
111
|
+
Your class should implement the `call` method which should return true or false as to whether it was successful or not.
|
112
|
+
|
113
|
+
If your finalizer returns false, the transition will be "rolled back" and the failing transition, as well as all previous transitions
|
114
|
+
will be rolled back. The roll back is performed by calling `rollback` on the finalizer. During the roll back the finalizer will be
|
115
|
+
set up a little differently and you have access to:
|
116
|
+
|
117
|
+
* `object` - The wrapped object that has been rolled back.
|
118
|
+
* `state` - The attempted desired state.
|
119
|
+
* `params` - A hash of params as set in the transition definition.
|
120
|
+
|
121
|
+
The wrapped object has an array `failure_messages` available for tracking reasons for invalid transitions. You may shovel
|
122
|
+
a reason (string) into this if you want to provide information on why your finalizer failed.
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
class WrapUp < EndState::Finalizer
|
126
|
+
def call
|
127
|
+
# Some important processing
|
128
|
+
true
|
129
|
+
end
|
130
|
+
|
131
|
+
def rollback
|
132
|
+
# Undo stuff that shouldn't have been done.
|
133
|
+
end
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
A finalizer can be added to the transition definition:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
class Machine < EndState::StateMachine
|
141
|
+
transition a: :b do |t|
|
142
|
+
t.finalizer WrapUp, option1: 'Some Option', option2: 'Some Other Option'
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
Since it is a common use case, a finalizer is included which will call `save` on the wrapped object if it responds to `save`.
|
148
|
+
You can use this with a convience method in your transition definition:
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
class Machine < EndState::StateMachine
|
152
|
+
transition a: :b do |t|
|
153
|
+
t.persistence_on
|
154
|
+
end
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
## Action
|
159
|
+
|
160
|
+
By default, a transition from one state to another is handled by `EndState` and only changes the state to the new state.
|
161
|
+
This is the recommended default and you should have a good reason to do something more or different.
|
162
|
+
If you really want to do something different though you can create a class that subclasses `EndState::Action` and implement
|
163
|
+
the `call` method.
|
164
|
+
|
165
|
+
You will have access to:
|
166
|
+
|
167
|
+
* `object` - The wrapped object.
|
168
|
+
* `state` - The desired state.
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
class MyCustomAction < EndState::Action
|
172
|
+
def call
|
173
|
+
# Do something special
|
174
|
+
super
|
175
|
+
end
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
class Machine < EndState::StateMachine
|
181
|
+
transition a: :b do |t|
|
182
|
+
t.custom_action MyCustomAction
|
183
|
+
end
|
184
|
+
end
|
185
|
+
```
|
186
|
+
|
187
|
+
## Exceptions for failing Transitions
|
188
|
+
|
189
|
+
By default `transition` will only raise an exception, `EndState::UnknownState`, if called with a state that doesn't exist.
|
190
|
+
All other failures, such as missing transition, guard failure, or finalizer failure will silently just return `false` and not
|
191
|
+
transition to the new state.
|
192
|
+
|
193
|
+
You also have the option to use `transition!` which will instead raise an error for failures. If your guards and/or finalizers
|
194
|
+
add to the `failure_messages` array then they will be included in the error message.
|
195
|
+
|
196
|
+
## Contributing
|
197
|
+
|
198
|
+
1. Fork it ( https://github.com/Originate/end_state/fork )
|
199
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
200
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
201
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
202
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/end_state.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'end_state/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'end_state'
|
8
|
+
spec.version = EndState::VERSION
|
9
|
+
spec.authors = ['alexpeachey']
|
10
|
+
spec.email = ['alex.peachey@gmail.com']
|
11
|
+
spec.summary = 'A State Machine implementation'
|
12
|
+
spec.description = 'A modular state machine with single responsibilities.'
|
13
|
+
spec.homepage = 'https://github.com/Originate/end_state'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
spec.add_development_dependency 'rspec'
|
24
|
+
spec.add_development_dependency 'rubocop'
|
25
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'end_state'
|
3
|
+
|
4
|
+
class Easy < EndState::Guard
|
5
|
+
def will_allow?
|
6
|
+
true
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class NoOp < EndState::Finalizer
|
11
|
+
def call
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def rollback
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class CustomAction < EndState::Action
|
21
|
+
def call
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Machine < EndState::StateMachine
|
27
|
+
transition a: :b do |t|
|
28
|
+
t.guard Easy, important_param: 'FOO!'
|
29
|
+
t.persistence_on
|
30
|
+
end
|
31
|
+
|
32
|
+
transition b: :c do |t|
|
33
|
+
t.custom_action CustomAction
|
34
|
+
t.persistence_on
|
35
|
+
end
|
36
|
+
|
37
|
+
transition [:b, :c] => :a do |t|
|
38
|
+
t.finalizer NoOp, not_very_important_param: 'Ignore me'
|
39
|
+
t.persistence_on
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class StatefulObject
|
44
|
+
attr_accessor :state
|
45
|
+
|
46
|
+
def initialize(state)
|
47
|
+
@state = state
|
48
|
+
end
|
49
|
+
|
50
|
+
def save
|
51
|
+
puts "Saved with state: #{state}"
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
object = StatefulObject.new(:a)
|
57
|
+
machine = Machine.new(object)
|
58
|
+
|
59
|
+
puts "The machine's class is: #{machine.class.name}"
|
60
|
+
puts "The machine's object class is: #{machine.object.class.name}"
|
61
|
+
puts
|
62
|
+
|
63
|
+
%i( b c a c).each do |state|
|
64
|
+
puts "Attempting to move to #{state}"
|
65
|
+
machine.transition state
|
66
|
+
puts "State: #{machine.state}"
|
67
|
+
predicate = "#{state}?".to_sym
|
68
|
+
puts "#{state}?: #{machine.send(predicate)}"
|
69
|
+
puts
|
70
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module EndState
|
2
|
+
class Finalizer
|
3
|
+
attr_reader :object, :state, :params
|
4
|
+
|
5
|
+
def initialize(object, state, params)
|
6
|
+
@object = object
|
7
|
+
@state = state
|
8
|
+
@params = params
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
def rollback
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module EndState
|
2
|
+
module Finalizers
|
3
|
+
class Persistence < EndState::Finalizer
|
4
|
+
def call
|
5
|
+
return false unless object.respond_to? :save
|
6
|
+
!!(object.save)
|
7
|
+
end
|
8
|
+
|
9
|
+
def rollback
|
10
|
+
return true unless object.respond_to? :save
|
11
|
+
!!(object.save)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'end_state/finalizers/persistence'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module EndState
|
2
|
+
class Guard
|
3
|
+
attr_reader :object, :state, :params
|
4
|
+
|
5
|
+
def initialize(object, state, params)
|
6
|
+
@object = object
|
7
|
+
@state = state
|
8
|
+
@params = params
|
9
|
+
end
|
10
|
+
|
11
|
+
def allowed?
|
12
|
+
will_allow?.tap do |result|
|
13
|
+
failed unless result
|
14
|
+
passed if result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def will_allow?
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def passed
|
23
|
+
end
|
24
|
+
|
25
|
+
def failed
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module EndState
|
2
|
+
class StateMachine < SimpleDelegator
|
3
|
+
attr_accessor :failure_messages
|
4
|
+
|
5
|
+
def self.transition(state_map, &block)
|
6
|
+
final_state = state_map.values.first
|
7
|
+
transition = Transition.new(final_state)
|
8
|
+
Array(state_map.keys.first).each do |state|
|
9
|
+
transitions[{ state => final_state }] = transition
|
10
|
+
end
|
11
|
+
yield transition if block
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.transitions
|
15
|
+
@transitions ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.state_attribute(attribute)
|
19
|
+
define_method(:state) { send(attribute.to_sym) }
|
20
|
+
define_method(:state=) { |val| send("#{attribute}=".to_sym, val) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.states
|
24
|
+
(start_states + end_states).uniq
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.start_states
|
28
|
+
transitions.keys.map { |state_map| state_map.keys.first }.uniq
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.end_states
|
32
|
+
transitions.keys.map { |state_map| state_map.values.first }.uniq
|
33
|
+
end
|
34
|
+
|
35
|
+
def object
|
36
|
+
__getobj__
|
37
|
+
end
|
38
|
+
|
39
|
+
def can_transition?(state)
|
40
|
+
previous_state = self.state
|
41
|
+
transition = self.class.transitions[{ previous_state => state }]
|
42
|
+
return block_transistion(transition, state, :soft) unless transition
|
43
|
+
transition.will_allow? state
|
44
|
+
end
|
45
|
+
|
46
|
+
def transition(state, mode = :soft)
|
47
|
+
@failure_messages = []
|
48
|
+
previous_state = self.state
|
49
|
+
transition = self.class.transitions[{ previous_state => state }]
|
50
|
+
return block_transistion(transition, state, mode) unless transition
|
51
|
+
return guard_failed(state, mode) unless transition.allowed?(self)
|
52
|
+
return false unless transition.action.new(self, state).call
|
53
|
+
return finalize_failed(state, mode) unless transition.finalize(self, previous_state)
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def transition!(state)
|
58
|
+
transition state, :hard
|
59
|
+
end
|
60
|
+
|
61
|
+
def method_missing(method, *args, &block)
|
62
|
+
check_state = method.to_s[0..-2].to_sym
|
63
|
+
return super unless self.class.states.include? check_state
|
64
|
+
if method.to_s.end_with?('?')
|
65
|
+
state == check_state
|
66
|
+
elsif method.to_s.end_with?('!')
|
67
|
+
transition check_state
|
68
|
+
else
|
69
|
+
super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def block_transistion(transition, state, mode)
|
76
|
+
if self.class.end_states.include? state
|
77
|
+
fail UnknownTransition, "The transition: #{object.state} => #{state} is unknown." if mode == :hard
|
78
|
+
return false
|
79
|
+
end
|
80
|
+
fail UnknownState, "The state: #{state} is unknown." unless transition
|
81
|
+
end
|
82
|
+
|
83
|
+
def guard_failed(state, mode)
|
84
|
+
return false unless mode == :hard
|
85
|
+
fail GuardFailed, "The transition to #{state} was blocked: #{failure_messages.join(', ')}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def finalize_failed(state, mode)
|
89
|
+
return false unless mode == :hard
|
90
|
+
fail FinalizerFailed, "The transition to #{state} was rolled back: #{failure_messages.join(', ')}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module EndState
|
2
|
+
class Transition
|
3
|
+
attr_reader :state
|
4
|
+
attr_accessor :action, :guards, :finalizers
|
5
|
+
|
6
|
+
def initialize(state)
|
7
|
+
@state = state
|
8
|
+
@action = Action
|
9
|
+
@guards = []
|
10
|
+
@finalizers = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def allowed?(object)
|
14
|
+
guards.all? { |guard| guard[:guard].new(object, state, guard[:params]).allowed? }
|
15
|
+
end
|
16
|
+
|
17
|
+
def will_allow?(object)
|
18
|
+
guards.all? { |guard| guard[:guard].new(object, state, guard[:params]).will_allow? }
|
19
|
+
end
|
20
|
+
|
21
|
+
def finalize(object, previous_state)
|
22
|
+
finalizers.each_with_object([]) do |finalizer, finalized|
|
23
|
+
finalized << finalizer
|
24
|
+
return rollback(finalized, object, previous_state) unless run_finalizer(finalizer, object, state)
|
25
|
+
end
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def custom_action(action)
|
30
|
+
@action = action
|
31
|
+
end
|
32
|
+
|
33
|
+
def guard(guard, params = {})
|
34
|
+
guards << { guard: guard, params: params }
|
35
|
+
end
|
36
|
+
|
37
|
+
def finalizer(finalizer, params = {})
|
38
|
+
finalizers << { finalizer: finalizer, params: params }
|
39
|
+
end
|
40
|
+
|
41
|
+
def persistence_on
|
42
|
+
finalizer Finalizers::Persistence
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def rollback(finalized, object, previous_state)
|
48
|
+
action.new(object, previous_state).rollback
|
49
|
+
finalized.reverse.each { |f| f[:finalizer].new(object, state, f[:params]).rollback }
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
def run_finalizer(finalizer, object, state)
|
54
|
+
finalizer[:finalizer].new(object, state, finalizer[:params]).call
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/end_state.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'end_state/version'
|
3
|
+
require 'end_state/errors'
|
4
|
+
require 'end_state/guard'
|
5
|
+
require 'end_state/finalizer'
|
6
|
+
require 'end_state/finalizers'
|
7
|
+
require 'end_state/transition'
|
8
|
+
require 'end_state/action'
|
9
|
+
require 'end_state/state_machine'
|
10
|
+
|
11
|
+
module EndState
|
12
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module EndState
|
4
|
+
describe Action do
|
5
|
+
subject(:action) { Action.new(object, state) }
|
6
|
+
let(:object) { OpenStruct.new(state: nil) }
|
7
|
+
let(:state) { :a }
|
8
|
+
|
9
|
+
describe '#call' do
|
10
|
+
it 'changes the state to the new state' do
|
11
|
+
action.call
|
12
|
+
expect(object.state).to eq :a
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#rollback' do
|
17
|
+
it 'changes the state to the new state' do
|
18
|
+
action.rollback
|
19
|
+
expect(object.state).to eq :a
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module EndState
|
4
|
+
module Finalizers
|
5
|
+
describe Persistence do
|
6
|
+
subject(:finalizer) { Persistence.new(object, state, params) }
|
7
|
+
let(:object) { double :object, save: nil }
|
8
|
+
let(:state) { :b }
|
9
|
+
let(:params) { {} }
|
10
|
+
|
11
|
+
describe '#call' do
|
12
|
+
it 'calls save on the object' do
|
13
|
+
finalizer.call
|
14
|
+
expect(object).to have_received(:save)
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when the object does not respond to save' do
|
18
|
+
let(:object) { Object.new }
|
19
|
+
|
20
|
+
it 'returns false' do
|
21
|
+
expect(finalizer.call).to be_false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#rollback' do
|
27
|
+
it 'calls save on the object' do
|
28
|
+
finalizer.rollback
|
29
|
+
expect(object).to have_received(:save)
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when the object does not respond to save' do
|
33
|
+
let(:object) { Object.new }
|
34
|
+
|
35
|
+
it 'returns true' do
|
36
|
+
expect(finalizer.rollback).to be_true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,327 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module EndState
|
5
|
+
describe StateMachine do
|
6
|
+
subject(:machine) { StateMachine.new(object) }
|
7
|
+
let(:object) { OpenStruct.new(state: nil) }
|
8
|
+
before { StateMachine.instance_variable_set '@transitions'.to_sym, nil }
|
9
|
+
|
10
|
+
describe '.transition' do
|
11
|
+
let(:state_map) { { a: :b } }
|
12
|
+
let(:yielded) { OpenStruct.new(transition: nil) }
|
13
|
+
before { StateMachine.transition(state_map) { |transition| yielded.transition = transition } }
|
14
|
+
|
15
|
+
it 'yields a transition for the supplied end state' do
|
16
|
+
expect(yielded.transition.state).to eq :b
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'does not require a block' do
|
20
|
+
expect(StateMachine.transition(b: :c)).not_to raise_error
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'adds the transition to the state machine' do
|
24
|
+
expect(StateMachine.transitions[state_map]).to eq yielded.transition
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '.state_attribute' do
|
29
|
+
context 'when set to :foobar' do
|
30
|
+
let(:object) { OpenStruct.new(foobar: :a) }
|
31
|
+
before { StateMachine.state_attribute :foobar }
|
32
|
+
|
33
|
+
it 'answers state with foobar' do
|
34
|
+
expect(machine.state).to eq object.foobar
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'answers state= with foobar=' do
|
38
|
+
machine.state = :b
|
39
|
+
expect(object.foobar).to eq :b
|
40
|
+
end
|
41
|
+
|
42
|
+
after do
|
43
|
+
StateMachine.send(:remove_method, :state)
|
44
|
+
StateMachine.send(:remove_method, :state=)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '.states' do
|
50
|
+
before do
|
51
|
+
StateMachine.transition(a: :b)
|
52
|
+
StateMachine.transition(b: :c)
|
53
|
+
end
|
54
|
+
|
55
|
+
specify { expect(StateMachine.states).to eq [:a, :b, :c] }
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '.start_states' do
|
59
|
+
before do
|
60
|
+
StateMachine.transition(a: :b)
|
61
|
+
StateMachine.transition(b: :c)
|
62
|
+
end
|
63
|
+
|
64
|
+
specify { expect(StateMachine.start_states).to eq [:a, :b] }
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '.end_states' do
|
68
|
+
before do
|
69
|
+
StateMachine.transition(a: :b)
|
70
|
+
StateMachine.transition(b: :c)
|
71
|
+
end
|
72
|
+
|
73
|
+
specify { expect(StateMachine.end_states).to eq [:b, :c] }
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#state' do
|
77
|
+
context 'when the object has state :a' do
|
78
|
+
let(:object) { OpenStruct.new(state: :a) }
|
79
|
+
|
80
|
+
specify { expect(machine.state).to eq :a }
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when the object has state :b' do
|
84
|
+
let(:object) { OpenStruct.new(state: :b) }
|
85
|
+
|
86
|
+
specify { expect(machine.state).to eq :b }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#{state}?' do
|
91
|
+
before do
|
92
|
+
StateMachine.transition a: :b
|
93
|
+
StateMachine.transition b: :c
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'when the object has state :a' do
|
97
|
+
let(:object) { OpenStruct.new(state: :a) }
|
98
|
+
|
99
|
+
specify { expect(machine.a?).to be_true }
|
100
|
+
specify { expect(machine.b?).to be_false }
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'when the object has state :b' do
|
104
|
+
let(:object) { OpenStruct.new(state: :b) }
|
105
|
+
|
106
|
+
specify { expect(machine.b?).to be_true }
|
107
|
+
specify { expect(machine.a?).to be_false }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe '#{state}!' do
|
112
|
+
let(:object) { OpenStruct.new(state: :a) }
|
113
|
+
before do
|
114
|
+
StateMachine.transition a: :b
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'transitions the state' do
|
118
|
+
machine.b!
|
119
|
+
expect(machine.state).to eq :b
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#can_transition?' do
|
124
|
+
let(:object) { OpenStruct.new(state: :a) }
|
125
|
+
before do
|
126
|
+
StateMachine.transition a: :b
|
127
|
+
StateMachine.transition b: :c
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'when asking about an allowed transition' do
|
131
|
+
specify { expect(machine.can_transition? :b).to be_true }
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'when asking about a disallowed transition' do
|
135
|
+
specify { expect(machine.can_transition? :c).to be_false }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe '#transition' do
|
140
|
+
context 'when the transition does not exist' do
|
141
|
+
it 'raises an unknown state error' do
|
142
|
+
expect { machine.transition(:no_state) }.to raise_error(UnknownState)
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'but the attempted state does exist' do
|
146
|
+
before { StateMachine.transition a: :b }
|
147
|
+
|
148
|
+
it 'returns false' do
|
149
|
+
expect(machine.transition(:b)).to be_false
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'when the transition does exist' do
|
155
|
+
before { object.state = :a }
|
156
|
+
|
157
|
+
context 'and no configuration is given' do
|
158
|
+
before { StateMachine.transition a: :b }
|
159
|
+
|
160
|
+
it 'transitions the state' do
|
161
|
+
machine.transition :b
|
162
|
+
expect(object.state).to eq :b
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'and a guard is configured' do
|
167
|
+
let(:guard) { double :guard, new: guard_instance }
|
168
|
+
let(:guard_instance) { double :guard_instance, allowed?: nil }
|
169
|
+
before do
|
170
|
+
StateMachine.transition a: :b
|
171
|
+
StateMachine.transitions[{ a: :b }].guards << { guard: guard, params: {} }
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'and the object satisfies the guard' do
|
175
|
+
before do
|
176
|
+
guard_instance.stub(:allowed?).and_return(true)
|
177
|
+
object.state = :a
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'transitions the state' do
|
181
|
+
machine.transition :b
|
182
|
+
expect(object.state).to eq :b
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context 'and the object does not satisfy the guard' do
|
187
|
+
before do
|
188
|
+
guard_instance.stub(:allowed?).and_return(false)
|
189
|
+
object.state = :a
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'does not transition the state' do
|
193
|
+
machine.transition :b
|
194
|
+
expect(object.state).to eq :a
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'and a finalizer is configured' do
|
200
|
+
before do
|
201
|
+
StateMachine.transition a: :b do |transition|
|
202
|
+
transition.persistence_on
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context 'and the finalizer is successful' do
|
207
|
+
before do
|
208
|
+
object.state = :a
|
209
|
+
object.stub(:save).and_return(true)
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'transitions the state' do
|
213
|
+
machine.transition :b
|
214
|
+
expect(object.state).to eq :b
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context 'and the finalizer fails' do
|
219
|
+
before do
|
220
|
+
object.state = :a
|
221
|
+
object.stub(:save).and_return(false)
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'does not transition the state' do
|
225
|
+
machine.transition :b
|
226
|
+
expect(object.state).to eq :a
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe '#transition!' do
|
234
|
+
context 'when the transition does not exist' do
|
235
|
+
it 'raises an unknown state error' do
|
236
|
+
expect { machine.transition!(:no_state) }.to raise_error(UnknownState)
|
237
|
+
end
|
238
|
+
|
239
|
+
context 'but the attempted state does exist' do
|
240
|
+
before { StateMachine.transition a: :b }
|
241
|
+
|
242
|
+
it 'returns false' do
|
243
|
+
expect { machine.transition!(:b) }.to raise_error(UnknownTransition)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context 'when the transition does exist' do
|
249
|
+
before { object.state = :a }
|
250
|
+
|
251
|
+
context 'and no configuration is given' do
|
252
|
+
before { StateMachine.transition a: :b }
|
253
|
+
|
254
|
+
it 'transitions the state' do
|
255
|
+
machine.transition! :b
|
256
|
+
expect(object.state).to eq :b
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
context 'and a guard is configured' do
|
261
|
+
let(:guard) { double :guard, new: guard_instance }
|
262
|
+
let(:guard_instance) { double :guard_instance, allowed?: nil }
|
263
|
+
before do
|
264
|
+
StateMachine.transition a: :b
|
265
|
+
StateMachine.transitions[{ a: :b }].guards << { guard: guard, params: {} }
|
266
|
+
end
|
267
|
+
|
268
|
+
context 'and the object satisfies the guard' do
|
269
|
+
before do
|
270
|
+
guard_instance.stub(:allowed?).and_return(true)
|
271
|
+
object.state = :a
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'transitions the state' do
|
275
|
+
machine.transition! :b
|
276
|
+
expect(object.state).to eq :b
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
context 'and the object does not satisfy the guard' do
|
281
|
+
before do
|
282
|
+
guard_instance.stub(:allowed?).and_return(false)
|
283
|
+
object.state = :a
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'does not transition the state' do
|
287
|
+
expect { machine.transition! :b }.to raise_error(GuardFailed)
|
288
|
+
expect(object.state).to eq :a
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
context 'and a finalizer is configured' do
|
294
|
+
before do
|
295
|
+
StateMachine.transition a: :b do |transition|
|
296
|
+
transition.persistence_on
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
context 'and the finalizer is successful' do
|
301
|
+
before do
|
302
|
+
object.state = :a
|
303
|
+
object.stub(:save).and_return(true)
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'transitions the state' do
|
307
|
+
machine.transition! :b
|
308
|
+
expect(object.state).to eq :b
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
context 'and the finalizer fails' do
|
313
|
+
before do
|
314
|
+
object.state = :a
|
315
|
+
object.stub(:save).and_return(false)
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'does not transition the state' do
|
319
|
+
expect { machine.transition! :b }.to raise_error(FinalizerFailed)
|
320
|
+
expect(object.state).to eq :a
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module EndState
|
5
|
+
describe Transition do
|
6
|
+
subject(:transition) { Transition.new(state) }
|
7
|
+
let(:state) { :a }
|
8
|
+
|
9
|
+
describe '#custom_action' do
|
10
|
+
let(:custom) { double :custom }
|
11
|
+
|
12
|
+
it 'sets the action' do
|
13
|
+
transition.custom_action custom
|
14
|
+
expect(transition.action).to eq custom
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#guard' do
|
19
|
+
let(:guard) { double :guard }
|
20
|
+
|
21
|
+
it 'adds a guard' do
|
22
|
+
expect { transition.guard guard }.to change(transition.guards, :count).by(1)
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when params are provided' do
|
26
|
+
let(:params) { {} }
|
27
|
+
|
28
|
+
it 'adds a guard' do
|
29
|
+
expect { transition.guard guard, params }.to change(transition.guards, :count).by(1)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#allowed?' do
|
35
|
+
let(:guard) { double :guard, new: guard_instance }
|
36
|
+
let(:guard_instance) { double :guard_instance, allowed?: nil }
|
37
|
+
before { transition.guards << { guard: guard, params: {} } }
|
38
|
+
|
39
|
+
context 'when all guards pass' do
|
40
|
+
let(:object) { double :object }
|
41
|
+
before { guard_instance.stub(:allowed?).and_return(true) }
|
42
|
+
|
43
|
+
specify { expect(transition.allowed? object).to be_true }
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when not all guards pass' do
|
47
|
+
let(:object) { double :object }
|
48
|
+
before { guard_instance.stub(:allowed?).and_return(false) }
|
49
|
+
|
50
|
+
specify { expect(transition.allowed? object).to be_false }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#will_allow?' do
|
55
|
+
let(:guard) { double :guard, new: guard_instance }
|
56
|
+
let(:guard_instance) { double :guard_instance, will_allow?: nil }
|
57
|
+
before { transition.guards << { guard: guard, params: {} } }
|
58
|
+
|
59
|
+
context 'when all guards pass' do
|
60
|
+
let(:object) { double :object }
|
61
|
+
before { guard_instance.stub(:will_allow?).and_return(true) }
|
62
|
+
|
63
|
+
specify { expect(transition.will_allow? object).to be_true }
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when not all guards pass' do
|
67
|
+
let(:object) { double :object }
|
68
|
+
before { guard_instance.stub(:will_allow?).and_return(false) }
|
69
|
+
|
70
|
+
specify { expect(transition.will_allow? object).to be_false }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#finalizer' do
|
75
|
+
let(:finalizer) { double :finalizer }
|
76
|
+
|
77
|
+
it 'adds a finalizer' do
|
78
|
+
expect { transition.finalizer finalizer }.to change(transition.finalizers, :count).by(1)
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'when params are provided' do
|
82
|
+
let(:params) { {} }
|
83
|
+
|
84
|
+
it 'adds a finalizer' do
|
85
|
+
expect { transition.finalizer finalizer, params }.to change(transition.finalizers, :count).by(1)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#persistence_on' do
|
91
|
+
it 'adds a Persistence finalizer' do
|
92
|
+
expect { transition.persistence_on }.to change(transition.finalizers, :count).by(1)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '#finalize' do
|
97
|
+
let(:finalizer) { double :finalizer, new: finalizer_instance }
|
98
|
+
let(:finalizer_instance) { double :finalizer_instance, call: nil, rollback: nil }
|
99
|
+
let(:object) { OpenStruct.new(state: :b) }
|
100
|
+
before { transition.finalizers << { finalizer: finalizer, params: {} } }
|
101
|
+
|
102
|
+
context 'when all finalizers succeed' do
|
103
|
+
before { finalizer_instance.stub(:call).and_return(true) }
|
104
|
+
|
105
|
+
specify { expect(transition.finalize object, :a).to be_true }
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'when not all finalizers succeed' do
|
109
|
+
before { finalizer_instance.stub(:call).and_return(false) }
|
110
|
+
|
111
|
+
specify { expect(transition.finalize object, :a).to be_false }
|
112
|
+
|
113
|
+
it 'rolls them back' do
|
114
|
+
transition.finalize object, :a
|
115
|
+
expect(finalizer_instance).to have_received(:rollback)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: end_state
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- alexpeachey
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: A modular state machine with single responsibilities.
|
70
|
+
email:
|
71
|
+
- alex.peachey@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".hound.yml"
|
78
|
+
- ".rspec"
|
79
|
+
- ".rubocop.yml"
|
80
|
+
- ".travis.yml"
|
81
|
+
- Gemfile
|
82
|
+
- LICENSE.txt
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- end_state.gemspec
|
86
|
+
- examples/example1.rb
|
87
|
+
- lib/end_state.rb
|
88
|
+
- lib/end_state/action.rb
|
89
|
+
- lib/end_state/errors.rb
|
90
|
+
- lib/end_state/finalizer.rb
|
91
|
+
- lib/end_state/finalizers.rb
|
92
|
+
- lib/end_state/finalizers/persistence.rb
|
93
|
+
- lib/end_state/guard.rb
|
94
|
+
- lib/end_state/state_machine.rb
|
95
|
+
- lib/end_state/transition.rb
|
96
|
+
- lib/end_state/version.rb
|
97
|
+
- spec/end_state/action_spec.rb
|
98
|
+
- spec/end_state/finalizers/persistence_spec.rb
|
99
|
+
- spec/end_state/state_machine_spec.rb
|
100
|
+
- spec/end_state/transition_spec.rb
|
101
|
+
- spec/end_state_spec.rb
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
homepage: https://github.com/Originate/end_state
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 2.2.2
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: A State Machine implementation
|
127
|
+
test_files:
|
128
|
+
- spec/end_state/action_spec.rb
|
129
|
+
- spec/end_state/finalizers/persistence_spec.rb
|
130
|
+
- spec/end_state/state_machine_spec.rb
|
131
|
+
- spec/end_state/transition_spec.rb
|
132
|
+
- spec/end_state_spec.rb
|
133
|
+
- spec/spec_helper.rb
|