finity 0.1.0 → 0.1.1

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.
data/README.md CHANGED
@@ -1,16 +1,15 @@
1
1
  # Finity
2
2
 
3
3
  **Finity** tries to be an extremly lightweight state machine implementation
4
- with an easily readable syntax which is essential if you have tens or hundreds
5
- of transitions. It is inspired by [transitions][], a great state machine
6
- implementation tightly integrated with ActiveRecord by Jakub Kuźma and
7
- Timo Rößner.
4
+ with an easily readable syntax. At the time of this writing, **Finity** is
5
+ comprised of only ~160 lines of code. It is inspired by [transitions][], a
6
+ great state machine implementation tightly integrated with ActiveRecord by
7
+ Jakub Kuźma and Timo Rößner.
8
8
 
9
- The goal of **Finity** is to provide a state machine implementation which is as
10
- slim and fast as possible while maintaining a beautiful and readable syntax. If
11
- you need ActiveModel/ActiveRecord integration,[transitions][] is your weapon of
12
- choice. However, if you only need a plain state machine implementation which is
13
- optimized for readability and efficiency, give **Finity** a spin.
9
+ The aim of **Finity** is to provide a state machine implementation which is as
10
+ slim and fast as possible while maintaining a beautiful and readable syntax.
11
+ However, if you need ActiveModel/ActiveRecord integration, [transitions][] may
12
+ be your weapon of choice.
14
13
 
15
14
  ## Installation
16
15
 
@@ -21,93 +20,233 @@ command line:
21
20
  gem 'finity'
22
21
  ```
23
22
 
24
- If you're not using Rails, you can install **Finity** with `gem` via command
23
+ Otherwise you can install **Finity** with `gem` via command
25
24
  line:
26
25
 
27
26
  ```
28
27
  gem install finity
29
28
  ```
30
29
 
31
- ## Usage
30
+ ## Example
32
31
 
33
- **Finity** can transform any class into a state machine. The only thing you
34
- have to do is to include it and define some transitions. For example, consider
35
- a state machine modelling the different states of reading the contents of a
36
- file:
32
+ **Finity** can transform any class into a state machine. For example, consider
33
+ a state machine modelling an *elevator* in a building with 3 floors: `ground`,
34
+ `first` and `second`. The *elevator* can perform the following actions:
35
+
36
+ * On floors `ground` and `first`, it can go `up` to the floor above.
37
+ * On floors `first` and `second`, it can go `down` to the floor below.
38
+
39
+ *Leaving* (*entering*) a floor, the doors need to `close` (`open`). Also, when
40
+ entering a floor or pressing `down` (`up`) on the `ground` (`second`) floor, a
41
+ bell should `ring`. The following class models the *elevator*:
37
42
 
38
43
  ``` ruby
39
- class Readfile
44
+ class Elevator
40
45
  include Finity
41
46
 
42
- finity :init => :opened do
43
-
44
- state :opened,
45
- :enter => proc { @file = File.open '...' }
47
+ finity :init => :ground do
46
48
 
47
- state :reading,
48
- :enter => proc { process @file.readline }
49
+ state :ground,
50
+ :enter => :open,
51
+ :cycle => :ring,
52
+ :leave => :close
49
53
 
50
- state :closed,
51
- :enter => proc { @file.close '...' }
54
+ state :first,
55
+ :enter => :open,
56
+ :leave => :close
52
57
 
53
- event :read do
54
- transitions :from => [:opened, :reading], :to => :reading,
55
- :if => proc { not @file.eof? },
56
- :do => proc { log 'Reading next line of file' }
58
+ state :second,
59
+ :enter => :open,
60
+ :cycle => :ring,
61
+ :leave => :close
57
62
 
58
- transitions :from => [:opened, :reading], :to => :reading,
59
- :do => proc { log 'Reached end of file' }
63
+ event :up do
64
+ transitions :from => [:ground], :to => :first
65
+ transitions :from => [:first, :second], :to => :second
60
66
  end
61
67
 
62
- event :close do
63
- transitions :from => [:opened, :reading], :to => :closed,
64
- :do => proc { log 'Closing file handle' }
68
+ event :down do
69
+ transitions :from => [:ground, :first], :to => :ground
70
+ transitions :from => [:second], :to => :first
65
71
  end
66
72
  end
73
+
74
+ def up
75
+ event! :up
76
+ end
77
+
78
+ def down
79
+ event! :down
80
+ end
81
+
82
+ private
83
+
84
+ def open
85
+ ring and puts "Doors opening on the #{@current} floor."
86
+ end
87
+
88
+ def ring
89
+ puts "Ring!"
90
+ end
91
+
92
+ def close
93
+ puts "Doors closing..."
94
+ end
67
95
  end
68
96
  ```
69
97
 
70
- ## States
98
+ While the different floors are modelled as *states*, the actions `up` and
99
+ `down` are modelled as *events*. The *elevator* is initialized on the `ground`
100
+ floor, which in this case is redundant, since, by default, **Finity** treats
101
+ the first state as the initial state. The instance variable `@current` holds
102
+ the current floor/state. The methods which are invoked upon entering, leaving
103
+ or cycling (staying in a state) are declared private, as they should not be
104
+ accessible from the outside. Only `up` and `down` are public.
105
+
106
+ We can now create an instance of the *elevator* and play with it:
107
+
108
+ ``` ruby
109
+ elevator = Elevator.new
110
+ elevator.down # => Ring!
111
+ elevator.up # => Doors closing...
112
+ # => Ring!
113
+ # => Doors opening on the first floor.
114
+ elevator.up # => Doors closing...
115
+ # => Ring!
116
+ # => Doors opening on the second floor.
117
+ elevator.up # => Ring!
118
+ elevator.down # => Doors closing.
119
+ # => Ring!
120
+ # => Doors opening on the first floor.
121
+ ```
122
+
123
+ While this example is very basic, it clearly shows the power of finite state
124
+ machines to model complex systems with a finite set of states and events
125
+ triggering transitions between them.
126
+
127
+ ## States, Events and Transitions
128
+
129
+ ### States
130
+
131
+ A state is uniquely identified by its name and *can* define functions to be
132
+ executed upon entering, leaving and cycling (staying inside). These functions
133
+ can be referenced as *Symbols*, *Strings*, *Procs* or *Lambdas*:
134
+
135
+ ``` ruby
136
+ state :ground,
137
+ :enter => :open, # Symbols must reference (private) methods
138
+ :cycle => proc { ring }, # Procs are evaluated in the context of the instance
139
+ :leave => -> elevator { elevator.close } # Lambdas are provided with the instance as an argument
140
+ ```
71
141
 
72
- A state is defined by its name and can define transition functions upon
73
- entering and leaving the state. These functions can be either referenced as
74
- Symbols, Strings, Procs or Lambda:
142
+ If there are several states with the same set of transition functions, they can
143
+ be defined in a single run. Considering our example, the `ground` and the
144
+ `second` floor bear the same set of actions, so we can combine them:
75
145
 
76
146
  ``` ruby
77
- state :some_state,
78
- :enter => proc { do_something and some_other_thing },
79
- :leave => :execute_leave_action!
147
+ state [:ground, :second],
148
+ :enter => :open,
149
+ :cycle => :ring,
150
+ :leave => :close
80
151
  ```
81
152
 
82
- ## Events and Transitions
153
+ ### Events and Transitions
83
154
 
84
- Events are like states defined by their names and can trigger several
85
- transitions from several states to other states. The transitions are evaluated
86
- in the order they are defined. If a valid transition is found, the execution is
87
- stopped and the transition performed:
155
+ Events are like states identified by their name and may define an arbitrary
156
+ number of transitions between different states. The transitions are evaluated
157
+ in the order in which they are defined. A transition is executed, if it is found
158
+ to be valid, which means that it contains the current state in `from` and the
159
+ `if`-guard, if defined, returns `true`. If no valid transition is found for a
160
+ given event, **Finity** will raise an error. Starting with our example, the
161
+ minimal information needed for the `up`-event is:
88
162
 
89
163
  ``` ruby
90
- event :some_event do
91
- transitions :from => [:some_state, :another_state], :to => :another_state,
92
- :if => proc { is_some_condition_true? },
93
- :do => :execute_something_upon_transition
164
+ event :up do
165
+ transitions :from => [:ground], :to => :first
166
+ transitions :from => [:first, :second], :to => :second
167
+ end
168
+ ```
169
+
170
+ Like for states, multiple events can be defined in a single run with the same
171
+ set of transitions:
94
172
 
95
- transitions :from => [:some_state], :to => :another_state,
96
- :do => :execute_something_else
173
+ ``` ruby
174
+ event [:up, :down] do
175
+ ...
176
+ end
177
+ ```
178
+
179
+ In case of an event, we sometimes want to take different actions from the same
180
+ state, so we need to specify *guards*. If a transition specifies a guard, it is
181
+ only considered valid if the guard returns `true`. For example, if we want to
182
+ deactivate the buttons when the elevator is `stuck`, we do the following:
183
+
184
+ ``` ruby
185
+ event :up do
186
+ transitions :from => [:ground], :to => :first,
187
+ :if => :not_stuck?
188
+ transitions :from => [:first, :second], :to => :second,
189
+ :if => :not_stuck?
190
+ transitions :from => [:ground, :first, :second, :stuck], :to => :stuck
97
191
  end
98
192
  ```
99
193
 
100
- Transitions can be guarded by decision functions (`:if`) and execute another
101
- function upon successful matching (`:do`). Transitions are triggered by the
102
- method `event!` which is defined for the including object. Many other state
103
- machine implementations define one method for each event and for each state,
104
- however, **Finity** tries to be as minimally invasive as possible:
194
+ This implies, that we defined a new state called `stuck` and a method to
195
+ determine whether the elevator is stuck at the moment. Unless `not_stuck?`
196
+ returns `true`, the elevator will keep working as in our original example.
197
+ Otherwise, only the last transition is valid and the elevator will enter
198
+ the `stuck` state from any other state.
199
+
200
+ Additionally, we can define functions to be executed for specific transitions
201
+ only. This can be achieved with `do`:
105
202
 
106
203
  ``` ruby
107
- object = SomeClassIncludingFinity.new
108
- if object.state? :some_state
109
- object.event! :some_event
204
+ event :up do
205
+ transitions :from => [:ground], :to => :first,
206
+ :if => :not_stuck?
207
+ transitions :from => [:first, :second], :to => :second,
208
+ :if => :not_stuck?
209
+ transitions :from => [:ground, :first, :second, :stuck], :to => :stuck,
210
+ :do => proc { puts "The elevator is stuck" }
110
211
  end
111
212
  ```
112
213
 
214
+ Now, if the elevator is stuck, a message is displayed everytime a button is
215
+ pushed. Like for states, all functions can be defined as *Symbols*, *Strings*,
216
+ *Procs* or *Lambdas*.
217
+
218
+ ### Definitions
219
+
220
+ **Finity** defines two methods on the including instance:
221
+
222
+ * `state? name`: Returns `true` if the state machine is in state `name`.
223
+ * `event! name`: Triggers event `name`.
224
+
225
+ Those methods can also be accessed from the outside. The current state is held
226
+ within the instance variable `@current`, contained in the including instance.
227
+
228
+ ## License
229
+
230
+ Copyright (c) 2012 Martin Donath
231
+
232
+ Permission is hereby granted, free of charge, to any person
233
+ obtaining a copy of this software and associated documentation files
234
+ (the "Software"), to deal in the Software without restriction,
235
+ including without limitation the rights to use, copy, modify, merge,
236
+ publish, distribute, sublicense, and/or sell copies of the Software,
237
+ and to permit persons to whom the Software is furnished to do so,
238
+ subject to the following conditions:
239
+
240
+ The above copyright notice and this permission notice shall be
241
+ included in all copies or substantial portions of the Software.
242
+
243
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
244
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
245
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
246
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
247
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
248
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
249
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
250
+ SOFTWARE.
251
+
113
252
  [transitions]: https://github.com/troessner/transitions
@@ -42,11 +42,12 @@ module Finity
42
42
 
43
43
  # Handle the current state and execute the first allowed transition.
44
44
  def handle object, state
45
+ raise InvalidState, "No match for (:#{state.name}) on (:#{name})" unless
46
+ @transitions.key? state.name
45
47
  @transitions[state.name].find do |transition|
46
48
  name = transition.handle object
47
49
  return name unless name.nil?
48
- end rescue
49
- raise InvalidState, "No handler for :#{name} from :#{state.name}"
50
+ end
50
51
  end
51
52
  end
52
53
  end
@@ -1,3 +1,3 @@
1
1
  module Finity
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.1'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: finity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-06 00:00:00.000000000 Z
12
+ date: 2012-08-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler