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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 756ccfa62e8a668e3f79a7a14d86a3d754a1f9fe
4
- data.tar.gz: ddcad78f959438aecdba5ceab40e851b8dbcd95e
3
+ metadata.gz: b674adab6efcd651b120fdef7af647d72f2a56b6
4
+ data.tar.gz: a9d6999adf6e44a93dc3d2961b6a47ab41eec00d
5
5
  SHA512:
6
- metadata.gz: 3c30391f82ffa5d1291a9d54ac0d76ca3dadbbbadec02e6a9ac1e369b406534ae968b2901c8650fa401d6391eec81089d7abb249d5e529d520959038dac824f6
7
- data.tar.gz: 134d43c549081bd25537ee095fabb793843d4d859ac33ca17014a4c64e37888a30dfee6d02dca0cf03b42d0c56cdb2dc676b7fbda2e195b1eeeea93740d93be8
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.
@@ -1,9 +1,9 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'aquam'
3
- s.version = '0.0.1'
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 = 'Aquam adds a DSL and validations to define a State Machine'
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'
@@ -3,5 +3,5 @@ require_relative 'aquam/machine'
3
3
  require_relative 'aquam/state'
4
4
 
5
5
  module Aquam
6
- VERSION = '0.0.1'
6
+ VERSION = '0.0.2'
7
7
  end
@@ -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
- private
11
+ class << self
12
+ def state_machine
13
+ @state_machine
14
+ end
29
15
 
30
- def self.define_event_methods
31
- state_machine.events.keys.each do |event|
32
- define_method event do
33
- fail Aquam::InvalidTransitionError
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
- def self.validate_state_machine(state_machine)
40
- unless state_machine.ancestors.include? Aquam::Machine
41
- fail Aquam::InvalidStateMachineError
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
@@ -1,14 +1,12 @@
1
1
  require 'aquam'
2
2
 
3
- class DoorState < Aquam::State; end
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 < DoorState
9
+ class ClosedDoorState < Aquam::State
12
10
  def open
13
11
  @object.state = :opened
14
12
  end
@@ -3,79 +3,86 @@ require_relative 'door_state_machine'
3
3
 
4
4
  describe Aquam::State do
5
5
  after do
6
- DoorState.class_variable_set :@@state_machine, nil
6
+ OpenedDoorState.instance_variable_set :@state_machine, nil
7
7
  end
8
8
 
9
- describe 'state_machine class method' do
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
- DoorState.state_machine String
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 entire hierarchy' do
17
- assert_equal nil, DoorState.state_machine
16
+ it 'defines the state machine that will be used in the state' do
17
+ assert_equal nil, OpenedDoorState.state_machine
18
18
 
19
- DoorState.state_machine DoorStateMachine
19
+ OpenedDoorState.use_machine DoorStateMachine
20
20
 
21
- assert_equal DoorStateMachine, DoorState.state_machine
21
+ assert_equal DoorStateMachine, OpenedDoorState.state_machine
22
22
  end
23
23
 
24
- it 'defines the state machine for every sublcass' do
25
- class OpenedDoorState < DoorState; end
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
- DoorState.state_machine DoorStateMachine
30
+ OpenedDoorState.use_machine DoorStateMachine
31
+ OpenedWindowState.use_machine WindowStateMachine
28
32
 
29
33
  assert_equal DoorStateMachine, OpenedDoorState.state_machine
30
- assert_equal DoorStateMachine, OpenedDoorState.new(nil).state_machine
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
- DoorState.state_machine DoorStateMachine
37
- DoorState.state_machine WindowStateMachine
43
+ OpenedDoorState.use_machine DoorStateMachine
44
+ OpenedDoorState.use_machine WindowStateMachine
38
45
 
39
- assert_equal DoorStateMachine, DoorState.state_machine
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
- DoorState.state_machine DoorStateMachine
52
+ OpenedDoorState.use_machine DoorStateMachine
46
53
 
47
- assert DoorState.instance_methods.include? :open
48
- assert DoorState.instance_methods.include? :close
49
- assert DoorState.instance_methods.include? :knock
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
- DoorState.state_machine DoorStateMachine
60
+ OpenedDoorState.use_machine DoorStateMachine
54
61
 
55
62
  assert_raises Aquam::InvalidTransitionError do
56
- DoorState.new(nil).open
63
+ OpenedDoorState.new(nil).open
57
64
  end
58
65
 
59
66
  assert_raises Aquam::InvalidTransitionError do
60
- DoorState.new(nil).close
67
+ OpenedDoorState.new(nil).close
61
68
  end
62
69
 
63
70
  assert_raises Aquam::InvalidTransitionError do
64
- DoorState.new(nil).knock
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
- DoorState.state_machine DoorStateMachine
78
+ OpenedDoorState.use_machine DoorStateMachine
72
79
 
73
- assert_equal DoorStateMachine, DoorState.new(nil).state_machine
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
- DoorState.new(nil).state_machine
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.1
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-15 00:00:00.000000000 Z
11
+ date: 2015-01-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Aquam adds a DSL and validations to define a State Machine
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: []