aquam 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/aquam.png)](http://badge.fury.io/rb/aquam)
|
3
|
+
[![Build Status](https://travis-ci.org/emancu/aquam.svg)](https://travis-ci.org/emancu/aquam)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/emancu/aquam/badges/gpa.svg)](https://codeclimate.com/github/emancu/aquam)
|
5
|
+
[![Dependency Status](https://gemnasium.com/emancu/aquam.svg)](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: []
|