finity 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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