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 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: []