aquam 0.0.1 → 0.0.2
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/README.md +164 -0
- data/aquam.gemspec +2 -2
- data/lib/aquam.rb +1 -1
- data/lib/aquam/state.rb +24 -28
- data/test/door_state_machine.rb +2 -4
- data/test/state_test.rb +32 -25
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b674adab6efcd651b120fdef7af647d72f2a56b6
|
4
|
+
data.tar.gz: a9d6999adf6e44a93dc3d2961b6a47ab41eec00d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3a6d9e78ab3b119d5605f27fb2069aad30727584b2e982b308528d8e2e53dbbabba41b06ee84abd9db23c15b8cdb54e19fcfb3d5c616b4acaed0691e7b99338
|
7
|
+
data.tar.gz: 035c2d436451a1324a78c3d1b8f3f00d6f384c257b5566824689edcb6fcfb77a47b87f1d89758035c59c4b029855698b68972d4dda10c88263f78fd5181448b8
|
data/README.md
CHANGED
@@ -1 +1,165 @@
|
|
1
1
|
# aquam
|
2
|
+
[](http://badge.fury.io/rb/aquam)
|
3
|
+
[](https://travis-ci.org/emancu/aquam)
|
4
|
+
[](https://codeclimate.com/github/emancu/aquam)
|
5
|
+
[](https://gemnasium.com/emancu/aquam)
|
6
|
+
|
7
|
+
A Ruby DSL for writing Finite State Machines and validate its transitions'
|
8
|
+
|
9
|
+
## Dependencies
|
10
|
+
|
11
|
+
`aquam` requires Ruby 2.1.x or later. No more dependencies.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
$ gem install aquam
|
16
|
+
|
17
|
+
# Getting started
|
18
|
+
|
19
|
+
`aquam` helps you to define _Finite State Machines_ with a very simple DSL which
|
20
|
+
also will validate events, states and the transition between them.
|
21
|
+
|
22
|
+
First of all, you must know that a State Machine should be a different object,
|
23
|
+
where you specify the valid states and the transitions fired by the events.
|
24
|
+
|
25
|
+
That being said, lets take a look how it works.
|
26
|
+
|
27
|
+
## Machine
|
28
|
+
|
29
|
+
Basically a Machine consists on
|
30
|
+
|
31
|
+
- states
|
32
|
+
- events
|
33
|
+
- transitions
|
34
|
+
|
35
|
+
There are three key words in our DSL that will help you to write your
|
36
|
+
own Finite State Machine, plus the `attribute` method.
|
37
|
+
|
38
|
+
### Example
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class DoorStateMachine < Aquam::Machine
|
42
|
+
state :opened, OpenedDoorState
|
43
|
+
state :closed, ClosedDoorState
|
44
|
+
|
45
|
+
event :open do
|
46
|
+
transition from: :closed, to: :opened
|
47
|
+
end
|
48
|
+
|
49
|
+
event :close do
|
50
|
+
transition from: :opened, to: :closed
|
51
|
+
end
|
52
|
+
|
53
|
+
event :knock do
|
54
|
+
transition from: :opened, to: :opened
|
55
|
+
transition from: :closed, to: :closed
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
> NOTE: `OpenedDoorState` and `ClosedDoorState` definitions are missing but
|
61
|
+
> we will cover States definition later.
|
62
|
+
|
63
|
+
### state
|
64
|
+
|
65
|
+
A `state` maps a symbol to a State class.
|
66
|
+
It tells to the _machine_ it is a valid state and which class represents it.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
state :opened, OpenedDoorState
|
70
|
+
```
|
71
|
+
|
72
|
+
### event
|
73
|
+
|
74
|
+
An `event` is a method which triggers the transition from one _state_ to another.
|
75
|
+
Each _state object_ must define **only** the events that are specified here.
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
event :open do
|
79
|
+
...
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
### transition
|
84
|
+
|
85
|
+
A `transition` moves the _state machine_ from **state A** to **state B**.
|
86
|
+
It can only be defined inside an `event` and you can define multiple transitions.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
transition from: :a_valid_state, to: :other_valid_state
|
90
|
+
```
|
91
|
+
|
92
|
+
### attribute
|
93
|
+
|
94
|
+
The `attribute` holds the name of the accessor in your own class where
|
95
|
+
the state name (string or symbol) will be stored.
|
96
|
+
|
97
|
+
By default uses `:state` as method accessor.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
attribute :state
|
101
|
+
```
|
102
|
+
|
103
|
+
### Extra
|
104
|
+
|
105
|
+
Being a subclass of `Aquam::Machine` also gives you some helpful **class methods**:
|
106
|
+
|
107
|
+
| Class Method | Description | Example (ruby) |
|
108
|
+
|:-------------|:-------------------------------------------------|:--------------------------------|
|
109
|
+
| states | `Hash` Valid states mapped to a class | `{ opened: OpenedDoorState }` |
|
110
|
+
| events | `Hash` Valid events with all its transitions | `{ open: { closed: :opened } }` |
|
111
|
+
| valid_state? | `Boolean` Check if it is a valid state | `true` |
|
112
|
+
| valid_event? | `Boolean` Check if it is a valid event | `true` |
|
113
|
+
|
114
|
+
And for **instance methods** it defines:
|
115
|
+
|
116
|
+
| Class Method | Description | Example (ruby) |
|
117
|
+
|:------------------|:-----------------------------------------|:------------------------------|
|
118
|
+
| current_state | `Aquam::State` Instance of current state | `#<ClosedDoorState:0x007...>` |
|
119
|
+
| trigger | `Aquam::State` Instance of the new state | `#<ClosedDoorState:0x008...>` |
|
120
|
+
| valid_state? | `Boolean` Check if it is a valid state | `true` |
|
121
|
+
| valid_event? | `Boolean` Check if it is a valid event | `true` |
|
122
|
+
| valid_transition? | `Boolean` Check if it is a valid event | `true` |
|
123
|
+
|
124
|
+
|
125
|
+
## State
|
126
|
+
|
127
|
+
For each state, we define a class that implements the corresponding _events_.
|
128
|
+
Every bit of behavior that is *state-dependent* should become a method in the class.
|
129
|
+
`aquam` uses metaprogramming to define methods for every single event listed
|
130
|
+
in the state machine used.
|
131
|
+
|
132
|
+
### Example
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
class OpenedDoorState < Aquam::State
|
136
|
+
use_machine DoorStateMachine
|
137
|
+
|
138
|
+
def close
|
139
|
+
# Do something
|
140
|
+
|
141
|
+
@object.state = :closed
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class ClosedDoorState < Aquam::State
|
146
|
+
use_machine DoorStateMachine
|
147
|
+
|
148
|
+
def open
|
149
|
+
# Do something
|
150
|
+
|
151
|
+
@object.state = :opened
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
### use_machine
|
157
|
+
|
158
|
+
This is the only method that you **must** call from every State class,
|
159
|
+
in order to define the interface according to the _state machine_.
|
160
|
+
Basically, it defines a method for every event defined in the _state machine_.
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
use_machine DoorStateMachine
|
164
|
+
```
|
165
|
+
> NOTE: You can not change its value and it is accessible from all subclasses.
|
data/aquam.gemspec
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'aquam'
|
3
|
-
s.version = '0.0.
|
3
|
+
s.version = '0.0.2'
|
4
4
|
s.date = Time.now.strftime('%Y-%m-%d')
|
5
5
|
s.summary = 'DSL to define State Machines'
|
6
|
-
s.description = '
|
6
|
+
s.description = 'A Ruby DSL for writing Finite State Machines and validate its transitions'
|
7
7
|
s.authors = ['Emiliano Mancuso']
|
8
8
|
s.email = ['emiliano.mancuso@gmail.com']
|
9
9
|
s.homepage = 'http://github.com/emancu/aquam'
|
data/lib/aquam.rb
CHANGED
data/lib/aquam/state.rb
CHANGED
@@ -1,22 +1,5 @@
|
|
1
1
|
module Aquam
|
2
2
|
class State
|
3
|
-
# I have to use a class variable because it **must** be the same value
|
4
|
-
# across all the children calsses.
|
5
|
-
#
|
6
|
-
# Also I need it defined always
|
7
|
-
@@state_machine = nil
|
8
|
-
|
9
|
-
def self.state_machine(state_machine = nil)
|
10
|
-
if state_machine && !@@state_machine
|
11
|
-
validate_state_machine state_machine
|
12
|
-
|
13
|
-
@@state_machine = state_machine
|
14
|
-
define_event_methods
|
15
|
-
end
|
16
|
-
|
17
|
-
@@state_machine
|
18
|
-
end
|
19
|
-
|
20
3
|
def initialize(object)
|
21
4
|
@object = object
|
22
5
|
end
|
@@ -25,22 +8,35 @@ module Aquam
|
|
25
8
|
self.class.state_machine || fail(Aquam::InvalidStateMachineError)
|
26
9
|
end
|
27
10
|
|
28
|
-
|
11
|
+
class << self
|
12
|
+
def state_machine
|
13
|
+
@state_machine
|
14
|
+
end
|
29
15
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
16
|
+
def use_machine(state_machine)
|
17
|
+
@state_machine ||= begin
|
18
|
+
validate_state_machine state_machine
|
19
|
+
define_event_methods state_machine
|
20
|
+
|
21
|
+
state_machine
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def define_event_methods(machine)
|
28
|
+
machine.events.keys.each do |event|
|
29
|
+
define_method event do
|
30
|
+
fail Aquam::InvalidTransitionError
|
31
|
+
end
|
34
32
|
end
|
35
33
|
end
|
36
|
-
end
|
37
|
-
private_class_method :define_event_methods
|
38
34
|
|
39
|
-
|
40
|
-
|
41
|
-
|
35
|
+
def validate_state_machine(machine)
|
36
|
+
unless machine && machine.ancestors.include?(Aquam::Machine)
|
37
|
+
fail Aquam::InvalidStateMachineError
|
38
|
+
end
|
42
39
|
end
|
43
40
|
end
|
44
|
-
private_class_method :validate_state_machine
|
45
41
|
end
|
46
42
|
end
|
data/test/door_state_machine.rb
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
require 'aquam'
|
2
2
|
|
3
|
-
class
|
4
|
-
|
5
|
-
class OpenedDoorState < DoorState
|
3
|
+
class OpenedDoorState < Aquam::State
|
6
4
|
def close
|
7
5
|
@object.state = :closed
|
8
6
|
end
|
9
7
|
end
|
10
8
|
|
11
|
-
class ClosedDoorState <
|
9
|
+
class ClosedDoorState < Aquam::State
|
12
10
|
def open
|
13
11
|
@object.state = :opened
|
14
12
|
end
|
data/test/state_test.rb
CHANGED
@@ -3,79 +3,86 @@ require_relative 'door_state_machine'
|
|
3
3
|
|
4
4
|
describe Aquam::State do
|
5
5
|
after do
|
6
|
-
|
6
|
+
OpenedDoorState.instance_variable_set :@state_machine, nil
|
7
7
|
end
|
8
8
|
|
9
|
-
describe '
|
9
|
+
describe 'use_machine class method' do
|
10
10
|
it 'fails if it is not a valid Aquam::Machine class' do
|
11
11
|
assert_raises Aquam::InvalidStateMachineError do
|
12
|
-
|
12
|
+
OpenedDoorState.use_machine String
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
it 'defines the state machine that will be used in the
|
17
|
-
assert_equal nil,
|
16
|
+
it 'defines the state machine that will be used in the state' do
|
17
|
+
assert_equal nil, OpenedDoorState.state_machine
|
18
18
|
|
19
|
-
|
19
|
+
OpenedDoorState.use_machine DoorStateMachine
|
20
20
|
|
21
|
-
assert_equal DoorStateMachine,
|
21
|
+
assert_equal DoorStateMachine, OpenedDoorState.state_machine
|
22
22
|
end
|
23
23
|
|
24
|
-
it 'defines
|
25
|
-
class
|
24
|
+
it 'defines two different state machines for two different states' do
|
25
|
+
class OpenedWindowState < Aquam::State; end
|
26
|
+
class WindowStateMachine < Aquam::Machine
|
27
|
+
state :opened, OpenedWindowState
|
28
|
+
end
|
26
29
|
|
27
|
-
|
30
|
+
OpenedDoorState.use_machine DoorStateMachine
|
31
|
+
OpenedWindowState.use_machine WindowStateMachine
|
28
32
|
|
29
33
|
assert_equal DoorStateMachine, OpenedDoorState.state_machine
|
30
|
-
assert_equal
|
34
|
+
assert_equal WindowStateMachine, OpenedWindowState.state_machine
|
35
|
+
|
36
|
+
Object.send(:remove_const, :WindowStateMachine)
|
37
|
+
Object.send(:remove_const, :OpenedWindowState)
|
31
38
|
end
|
32
39
|
|
33
40
|
it 'defines the state machine only once' do
|
34
41
|
class WindowStateMachine < Aquam::Machine; end
|
35
42
|
|
36
|
-
|
37
|
-
|
43
|
+
OpenedDoorState.use_machine DoorStateMachine
|
44
|
+
OpenedDoorState.use_machine WindowStateMachine
|
38
45
|
|
39
|
-
assert_equal DoorStateMachine,
|
46
|
+
assert_equal DoorStateMachine, OpenedDoorState.state_machine
|
40
47
|
|
41
48
|
Object.send(:remove_const, :WindowStateMachine)
|
42
49
|
end
|
43
50
|
|
44
51
|
it 'defines all the events as methods' do
|
45
|
-
|
52
|
+
OpenedDoorState.use_machine DoorStateMachine
|
46
53
|
|
47
|
-
assert
|
48
|
-
assert
|
49
|
-
assert
|
54
|
+
assert OpenedDoorState.instance_methods.include? :open
|
55
|
+
assert OpenedDoorState.instance_methods.include? :close
|
56
|
+
assert OpenedDoorState.instance_methods.include? :knock
|
50
57
|
end
|
51
58
|
|
52
59
|
it 'fails by default on every event method' do
|
53
|
-
|
60
|
+
OpenedDoorState.use_machine DoorStateMachine
|
54
61
|
|
55
62
|
assert_raises Aquam::InvalidTransitionError do
|
56
|
-
|
63
|
+
OpenedDoorState.new(nil).open
|
57
64
|
end
|
58
65
|
|
59
66
|
assert_raises Aquam::InvalidTransitionError do
|
60
|
-
|
67
|
+
OpenedDoorState.new(nil).close
|
61
68
|
end
|
62
69
|
|
63
70
|
assert_raises Aquam::InvalidTransitionError do
|
64
|
-
|
71
|
+
OpenedDoorState.new(nil).knock
|
65
72
|
end
|
66
73
|
end
|
67
74
|
end
|
68
75
|
|
69
76
|
describe 'state_machine instance method' do
|
70
77
|
it 'returns the state machine class defined' do
|
71
|
-
|
78
|
+
OpenedDoorState.use_machine DoorStateMachine
|
72
79
|
|
73
|
-
assert_equal DoorStateMachine,
|
80
|
+
assert_equal DoorStateMachine, OpenedDoorState.new(nil).state_machine
|
74
81
|
end
|
75
82
|
|
76
83
|
it 'fails if the state machine was not defined' do
|
77
84
|
assert_raises Aquam::InvalidStateMachineError do
|
78
|
-
|
85
|
+
OpenedDoorState.new(nil).state_machine
|
79
86
|
end
|
80
87
|
end
|
81
88
|
end
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aquam
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Emiliano Mancuso
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-01-
|
11
|
+
date: 2015-01-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
13
|
+
description: A Ruby DSL for writing Finite State Machines and validate its transitions
|
14
14
|
email:
|
15
15
|
- emiliano.mancuso@gmail.com
|
16
16
|
executables: []
|