interstate_machine 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 +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +92 -0
- data/Rakefile +6 -0
- data/interstate_machine.gemspec +33 -0
- data/lib/interstate_machine/active_record_class/instance_methods.rb +11 -0
- data/lib/interstate_machine/base/instance_methods.rb +51 -0
- data/lib/interstate_machine/environment.rb +16 -0
- data/lib/interstate_machine/plain_ruby/instance_methods.rb +13 -0
- data/lib/interstate_machine/state_machine.rb +43 -0
- data/lib/interstate_machine/version.rb +3 -0
- data/lib/interstate_machine.rb +65 -0
- metadata +173 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ffa91c86831fa5b11b22c705b710b42a33a0e8b2
|
4
|
+
data.tar.gz: bc7349ab2a864157ee0c03ff7f5ac8fb4a4cf3a2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e656466c3918cf7ab0cba559dccb8238ebf03d864970666532511c70a2c2dcadd1fd07b62f0616a6bd8d111e9b8aefba533b421db23d1060ca31e627566a875a
|
7
|
+
data.tar.gz: b9c00fdebd2851d3fbfffe5eca3fc64770a46e75836505e8c18e679b0c77d4aea6f91753fbb6a999843598f9cff2499235471fc0102813fffbb2010637f80b4e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.4.0
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 SamuelMartini
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# InterstateMachine
|
2
|
+
When state machine meets interactor. InterstateMachine is a simple state machine which use interactors to trigger transitions. Long story short, an object receives an event which is a interactor and you can do fantastic things with interactors.
|
3
|
+
What is an interactor?
|
4
|
+
[*"An interactor is a simple, single-purpose object."*](https://github.com/collectiveidea/interactor)
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
```ruby
|
8
|
+
gem install interstate_machine
|
9
|
+
```
|
10
|
+
|
11
|
+
Gemfile
|
12
|
+
```ruby
|
13
|
+
gem 'interstate_machine', '~> 1.0.0'
|
14
|
+
```
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
```ruby
|
18
|
+
class TrafficLight < ActiveRecord::Base
|
19
|
+
include InterstateMachine
|
20
|
+
|
21
|
+
initial_state :stop
|
22
|
+
|
23
|
+
transition_table :stop, :proceed, :caution, :tilt, :broken do
|
24
|
+
on event: :cycle do |event|
|
25
|
+
allow event: event, transition_to: [:proceed], from: [:stop]
|
26
|
+
allow event: event, transition_to: [:caution], from: [:proceed]
|
27
|
+
allow event: event, transition_to: [:stop], from: [:caution]
|
28
|
+
end
|
29
|
+
on event: :tilt, transition_to: [:broken], from: [:proceed, :caution, :stop]
|
30
|
+
on event: :repair, transition_to: [:stop], from: [:broken]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
If you want to use `InterstateMachine` in plain ruby, add `attr_accessor :state` to store the state.
|
35
|
+
`transition_table` is where the state machine rules and states are defined.
|
36
|
+
Each event represent an `Interactor` that is called to process the transition.
|
37
|
+
|
38
|
+
`on` can take a block which defines different transition(rules) for the same event or a single transition
|
39
|
+
|
40
|
+
In addition to the class where you define the state machine, you also need to create interactors for each event.
|
41
|
+
|
42
|
+
In this case we have an event `cycle` that trigger many transitions so we define three interactors for the `cycle` event and two for the remaining.
|
43
|
+
`CycleProceed`, `CycleCaution`, `CycleStop` and then `Tilt` and `Repair`. Yes, when an event triggers a transition to a single state and it's not a block, you have to name the class like the event name.
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
class CycleProceed
|
47
|
+
include Interactor
|
48
|
+
|
49
|
+
before :validate_transition
|
50
|
+
|
51
|
+
def call
|
52
|
+
# do stuff needed when this state happen
|
53
|
+
'yeah'
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def validate_transition
|
59
|
+
context.fail!(error: 'there is no power') if context.object.power == 0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
Note: You can use all the magics like [hooks](https://github.com/collectiveidea/interactor). Whoop!
|
64
|
+
|
65
|
+
You can access the class where you have included InterstateMachine by `context.object`
|
66
|
+
|
67
|
+
When transition is allowed:
|
68
|
+
```ruby
|
69
|
+
t = TrafficLight.new
|
70
|
+
t.cycle
|
71
|
+
#=> :proceed
|
72
|
+
```
|
73
|
+
|
74
|
+
When transition can't happen because something wrong executing the event
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
t.power = 0
|
78
|
+
t.cycle
|
79
|
+
#=> 'there is no power'
|
80
|
+
t.state
|
81
|
+
#=> :stop
|
82
|
+
```
|
83
|
+
|
84
|
+
When transition is no allowed
|
85
|
+
```ruby
|
86
|
+
t.state
|
87
|
+
#=> :stop
|
88
|
+
t.repair
|
89
|
+
#=> RuntimeError Exception:
|
90
|
+
```
|
91
|
+
## Contributing
|
92
|
+
Feel free to play around, fork, add, pull request, and get a hug. If you decide to pull request please add tests
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'interstate_machine/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "interstate_machine"
|
8
|
+
spec.version = InterstateMachine::VERSION
|
9
|
+
spec.authors = ["SamuelMartini"]
|
10
|
+
spec.email = ["samueljmartini@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = 'a state machine with interactor implementation'
|
13
|
+
spec.description = 'InterstateMachine is a simple state machine which use interactors to trigger transitions'
|
14
|
+
spec.homepage = 'https://github.com/SamuelMartini/interstate_machine'
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_dependency "interactor", "~> 3.1"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.6"
|
29
|
+
spec.add_development_dependency 'byebug', "~> 9.1"
|
30
|
+
spec.add_development_dependency 'activerecord', "~> 5.1"
|
31
|
+
spec.add_development_dependency "sqlite3", "~> 1.3"
|
32
|
+
spec.add_development_dependency 'simplecov', "~> 0.15"
|
33
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module InterstateMachine
|
2
|
+
module Base
|
3
|
+
module InstanceMethods
|
4
|
+
|
5
|
+
def states
|
6
|
+
state_machine.states
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def evaluate_transition(event, transition_to, from)
|
12
|
+
if event_with_multiple_state_transition?(event, transition_to, from)
|
13
|
+
send("#{event}_#{state}")
|
14
|
+
elsif event_with_single_state_transition?(event, transition_to, from)
|
15
|
+
ensure_can_transit(event, transition_to, from)
|
16
|
+
else
|
17
|
+
raise "cannot transition via #{event} from #{state}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def ensure_can_transit(event, transition_to, from, multiple: false)
|
22
|
+
@state_machine.evaluate_transition_by!(
|
23
|
+
Interactor::Context.new(
|
24
|
+
event: event, transition_to: transition_to, from: from,
|
25
|
+
multiple: multiple, object: self
|
26
|
+
)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def event_with_single_state_transition?(event, transition_to, from)
|
31
|
+
respond_to?(event) && transition_to && from
|
32
|
+
end
|
33
|
+
|
34
|
+
def event_with_multiple_state_transition?(event, _transition_to, _from)
|
35
|
+
respond_to?("#{event}_#{state}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def constantize(event)
|
39
|
+
Object.const_get(event.to_s.split('_').collect(&:capitalize).join)
|
40
|
+
end
|
41
|
+
|
42
|
+
def interactor_name(event)
|
43
|
+
if state_machine.context.multiple
|
44
|
+
"#{event}_#{state_machine.next_state}"
|
45
|
+
else
|
46
|
+
event
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module InterstateMachine
|
2
|
+
class Environment
|
3
|
+
|
4
|
+
def self.define(base)
|
5
|
+
if active_record?(base)
|
6
|
+
base.send(:include, ActiveRecordClass::InstanceMethods)
|
7
|
+
else
|
8
|
+
base.send(:prepend, PlainRuby::InstanceMethods)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.active_record?(base)
|
13
|
+
base.ancestors.include?(ActiveRecord::Base) rescue false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module InterstateMachine
|
2
|
+
class StateMachine
|
3
|
+
attr_reader :states, :context
|
4
|
+
attr_accessor :state
|
5
|
+
|
6
|
+
def initialize(object)
|
7
|
+
@states = object.class.machine_states
|
8
|
+
@state = defined_state(object)
|
9
|
+
end
|
10
|
+
|
11
|
+
def evaluate_transition_by!(rule)
|
12
|
+
@context = rule
|
13
|
+
raise failed_transition unless context.from.include? context.object.state.to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
def next_state
|
17
|
+
context.transition_to.first
|
18
|
+
end
|
19
|
+
|
20
|
+
def next
|
21
|
+
context.object.state = context.transition_to.first
|
22
|
+
context.object.save
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def failed_transition
|
28
|
+
"can not transit to #{context.transition_to} from #{state} via
|
29
|
+
#{context.event}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def defined_state(object)
|
33
|
+
# TODO quite ugly, can be improved
|
34
|
+
if object.state.nil?
|
35
|
+
object.state = object.class.initialized_state.to_sym
|
36
|
+
object.save if object.respond_to?(:persisted?) && object.persisted?
|
37
|
+
object.state
|
38
|
+
else
|
39
|
+
object.state.to_sym
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'interstate_machine/version'
|
2
|
+
require 'interstate_machine/state_machine'
|
3
|
+
require 'interstate_machine/environment'
|
4
|
+
require 'interstate_machine/base/instance_methods'
|
5
|
+
require 'interstate_machine/plain_ruby/instance_methods'
|
6
|
+
|
7
|
+
require 'interstate_machine/active_record_class/instance_methods'
|
8
|
+
require 'interactor'
|
9
|
+
|
10
|
+
module InterstateMachine
|
11
|
+
def self.included(base)
|
12
|
+
base.class_eval do
|
13
|
+
Environment.define(base)
|
14
|
+
include Base::InstanceMethods
|
15
|
+
extend ClassMethods
|
16
|
+
|
17
|
+
private
|
18
|
+
attr_reader :state_machine
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
def initial_state(state)
|
24
|
+
@initialized_state = state
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialized_state
|
28
|
+
@initialized_state ||= []
|
29
|
+
end
|
30
|
+
|
31
|
+
def machine_states
|
32
|
+
@machine_states ||= []
|
33
|
+
end
|
34
|
+
|
35
|
+
def transition_table(*states)
|
36
|
+
@machine_states = states
|
37
|
+
yield
|
38
|
+
end
|
39
|
+
|
40
|
+
def on(event:, transition_to: nil, from: nil)
|
41
|
+
yield(event) if block_given?
|
42
|
+
perform_transition_by(
|
43
|
+
event: event,
|
44
|
+
transition_to: transition_to,
|
45
|
+
from: from
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def allow(event: nil, transition_to: nil, from: nil)
|
50
|
+
define_method "#{event}_#{from.first}" do
|
51
|
+
ensure_can_transit(event, transition_to, from, multiple: true)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def perform_transition_by(event: nil, transition_to: nil, from: nil)
|
58
|
+
define_method event do
|
59
|
+
evaluate_transition(event, transition_to, from)
|
60
|
+
action = constantize(interactor_name(event)).call(object: self)
|
61
|
+
action.success? ? @state_machine.next : action.error
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: interstate_machine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- SamuelMartini
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-01-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: interactor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.14'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.14'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.6'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.6'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '9.1'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '9.1'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activerecord
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '5.1'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '5.1'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sqlite3
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.3'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.3'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.15'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.15'
|
125
|
+
description: InterstateMachine is a simple state machine which use interactors to
|
126
|
+
trigger transitions
|
127
|
+
email:
|
128
|
+
- samueljmartini@gmail.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- ".gitignore"
|
134
|
+
- ".rspec"
|
135
|
+
- ".ruby-version"
|
136
|
+
- ".travis.yml"
|
137
|
+
- Gemfile
|
138
|
+
- LICENSE.txt
|
139
|
+
- README.md
|
140
|
+
- Rakefile
|
141
|
+
- interstate_machine.gemspec
|
142
|
+
- lib/interstate_machine.rb
|
143
|
+
- lib/interstate_machine/active_record_class/instance_methods.rb
|
144
|
+
- lib/interstate_machine/base/instance_methods.rb
|
145
|
+
- lib/interstate_machine/environment.rb
|
146
|
+
- lib/interstate_machine/plain_ruby/instance_methods.rb
|
147
|
+
- lib/interstate_machine/state_machine.rb
|
148
|
+
- lib/interstate_machine/version.rb
|
149
|
+
homepage: https://github.com/SamuelMartini/interstate_machine
|
150
|
+
licenses:
|
151
|
+
- MIT
|
152
|
+
metadata: {}
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
requirements: []
|
168
|
+
rubyforge_project:
|
169
|
+
rubygems_version: 2.6.13
|
170
|
+
signing_key:
|
171
|
+
specification_version: 4
|
172
|
+
summary: a state machine with interactor implementation
|
173
|
+
test_files: []
|