anthonyw-simple_state 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,38 +10,49 @@ There are several existing implementations of state machines in Ruby, notably
10
10
  cumbersome, when all I really needed was a lightweight means for setting the
11
11
  state of a class instance, and transitioning from one state to another.
12
12
 
13
- There is no support for adding your own code to customise transitions, nor is
14
- is there any support for callbacks or event guards. It's called
15
- **Simple**State for a reason! The library adds some helper methods to your
16
- class, keeps track of the valid states, makes sure that a transition is
17
- permitted, and that's about it.
13
+ There is no explicit support for adding your own code to customise
14
+ transitions, nor is there any support for callbacks or event guards (although
15
+ you can still do similar things fairly trivially). It's called **Simple**State
16
+ for a reason! The library adds some helper methods to your class, keeps track
17
+ of the valid states, makes sure that a transition is permitted, and that's
18
+ about it.
18
19
 
19
20
  ## Why use SimpleState?
20
21
 
21
- * Lightweight.
22
- * method_missing isn't used. ;)
23
- * No dependencies.
24
- * No extensions to core classes.
25
- * Tested on Ruby 1.8.6 (p287), 1.8.7 (p72), and 1.9.1 (p0).
26
- * Uses an API similar to Workflow, which I find to be more logical than that
27
- in the acts\_as\_state\_machine family.
22
+ <ul style="margin-top: 1em">
23
+ <li>Lightweight.</li>
24
+ <li>method_missing isn't used. ;)</li>
25
+ <li>No dependencies.</li>
26
+ <li>No extensions to core classes.</li>
27
+ <li>Tested on Ruby 1.8.6 (p287), 1.8.7 (p72), and 1.9.1 (p0).</li>
28
+ <li>Uses an API similar to Workflow, which I find to be more logical than
29
+ that in the acts_as_state_machine family.</li>
30
+ </ul>
28
31
 
29
32
  ## Why use something else?
30
33
 
31
- * SimpleState has no support for customising transitions with your own code.
32
- * No support for callbacks.
33
- * No support for guard conditions.
34
- * SimpleState forces you to use an attribute called `state` - other libraries
35
- let you choose whatever name you want.
36
- * Uses a class variable to keep track of transitions - doesn't lend itself
37
- all that well to subclassing your state machines.
38
-
39
- The three libraries mentioned above have support for callbacks and guard
40
- conditions: if you need these features then you'd be better off choosing one
41
- of those libraries instead of SimpleState.
34
+ <ul style="margin-top: 1em">
35
+ <li>The three libraries mentioned above make available, as part of their
36
+ DSL, a means of customising events/transitions with your own code.
37
+ SimpleState makes no such provision, however you can mimic the behaviour
38
+ quite easily as documented in example 3, below.</li>
39
+ <li>Similarly, some other libraries provide the ability to add guard
40
+ conditions -- a condition which must be satisfied before a transition
41
+ can take place. SimpleState also does explicitly support this, however it
42
+ is possible by adapting example 3.
43
+ <li>SimpleState forces you to use an attribute called `state` - other libraries
44
+ let you choose whatever name you want.</li>
45
+ <li>Uses a class variable to keep track of transitions - doesn't lend itself
46
+ all that well to subclassing your state machines.</li>
47
+ </ul>
48
+
49
+ If SimpleState's limitations are too much for you, then you are probably
50
+ better off choosing one of the other libraries instead.
42
51
 
43
52
  ## Examples
44
53
 
54
+ ### Example 1: Basic usage
55
+
45
56
  require 'rubygems'
46
57
  require 'simple_state'
47
58
 
@@ -96,13 +107,15 @@ state of the instance.
96
107
 
97
108
  # etc...
98
109
 
110
+ ### Example 2: Events in multiple states
111
+
99
112
  It is possible for the same event to be used in multiple states:
100
113
 
101
114
  state :not_started do
102
115
  event :start, :transitions_to => :started
103
116
  event :cancel, :transitions_to => :cancelled # <--
104
117
  end
105
-
118
+
106
119
  state :started do
107
120
  event :finish, :transitions_to => :finished
108
121
  event :cancel, :transitions_to => :cancelled # <--
@@ -125,6 +138,73 @@ current state:
125
138
  state :cancelled
126
139
  state :cancelled_before_start
127
140
 
141
+ ### Example 3: Customising event transitions
142
+
143
+ If the built in event methods aren't sufficient and you need to do extra stuff
144
+ to your class during a particular event, you can simply override the method;
145
+ the original method is available via `super`:
146
+
147
+ class OverriddenEvent
148
+ extend SimpleState
149
+
150
+ state_machine do
151
+ state :start do
152
+ event :start, :transitions_to => :started
153
+ end
154
+
155
+ state :started
156
+ end
157
+
158
+ def start!
159
+ puts "Before super() : state=#{self.state}"
160
+ ret = super
161
+ puts "After super() : state=#{self.state}"
162
+ ret
163
+ end
164
+ end
165
+
166
+ OverriddenEvent.new.start!
167
+ # => Before super() : state=start
168
+ # => After super() : state=finished
169
+ # => :started
170
+
171
+ If the event transition isn't valid, super will simply return false, otherwise
172
+ it will return the symbol representing the new state.
173
+
174
+ def start!
175
+ if new_state = super
176
+ puts "Started! The new state is #{self.state}"
177
+ else
178
+ puts "Could not start!"
179
+ end
180
+
181
+ new_state
182
+ end
183
+
184
+ machine = OverriddenEvent.new
185
+ machine.start!
186
+ => Started! The new state is finished
187
+ => :started
188
+
189
+ machine.start!
190
+ => Could not start!
191
+ => false
192
+
193
+ If you need to know whether a transition will be permitted before you call
194
+ super(), SimpleState provides `#event_permitted?`, expecting you to provide a
195
+ symbol representing the event.
196
+
197
+ machine.event_permitted?(:start)
198
+ # => true|false
199
+
200
+ This also provides an easy means for creating guard conditions:
201
+
202
+ def start!
203
+ if event_permitted?(:start) && SomeExternalService.can_start?(self)
204
+ super
205
+ end
206
+ end
207
+
128
208
  ## ORM Integration
129
209
 
130
210
  SimpleState should play nicely with your ORM of choice. When an object's state
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 3
2
+ :minor: 2
3
+ :patch: 0
3
4
  :major: 0
4
- :minor: 1
@@ -32,11 +32,31 @@ module SimpleState
32
32
 
33
33
  # Create an anonymous module which will be added to the state machine
34
34
  # class's inheritance chain.
35
- mod = @mod = Module.new do
35
+ mod = @mod = Module.new
36
+ mod.class_eval <<-RUBY, __FILE__, __LINE__ + 1
36
37
  def self.inspect
37
- "SimpleState::#{@klass}StateMachine"
38
+ "SimpleState::#{@klass}AnonMixin"
38
39
  end
39
- end
40
+
41
+ # Handles the change of state.
42
+ # @api private
43
+ def _change_state_using_event!(event)
44
+ self.state = self.class._determine_new_state(self.state, event)
45
+ end
46
+
47
+ # Returns if the passed event is permitted with the instance in it's
48
+ # current state.
49
+ # @api public
50
+ def event_permitted?(event)
51
+ self.class._event_permitted?(self.state, event)
52
+ end
53
+
54
+ # Returns true if the given symbol matches the current state.
55
+ # @api public
56
+ def in_state?(state)
57
+ self.state == state
58
+ end
59
+ RUBY
40
60
 
41
61
  # Declare the state machine rules.
42
62
  instance_eval(&blk)
@@ -61,9 +81,9 @@ module SimpleState
61
81
  @klass.initial_state ||= name
62
82
 
63
83
  @mod.class_eval <<-RUBY, __FILE__, __LINE__ + 1
64
- def #{name}? # def prepared?
65
- self.state == :#{name} # self.state == :prepared
66
- end # end
84
+ def #{name}? # def prepared?
85
+ in_state?(:#{name}) # self.state == :prepared
86
+ end # end
67
87
  RUBY
68
88
 
69
89
  # Define transitions for this state.
@@ -107,7 +127,7 @@ module SimpleState
107
127
  # Example:
108
128
  #
109
129
  # def process!
110
- # if self.class._valid_transition?(self.state, :process)
130
+ # if self.class._transition_permitted?(self.state, :process)
111
131
  # self.state =
112
132
  # self.class._determine_new_state(self.state, :process)
113
133
  # else
@@ -116,9 +136,8 @@ module SimpleState
116
136
  # end
117
137
  @module.class_eval <<-RUBY, __FILE__, __LINE__ + 1
118
138
  def #{event}!
119
- if self.class._valid_transition?(self.state, :#{event})
120
- self.state =
121
- self.class._determine_new_state(self.state, :#{event})
139
+ if event_permitted?(:#{event})
140
+ _change_state_using_event!(:#{event})
122
141
  else
123
142
  false
124
143
  end
@@ -42,7 +42,7 @@ module SimpleState
42
42
  end
43
43
 
44
44
  # @api private
45
- def _valid_transition?(current, to)
45
+ def _event_permitted?(current, to)
46
46
  states[current] and not states[current].assoc(to).nil?
47
47
  end
48
48
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anthonyw-simple_state
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anthony Williams
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-10 00:00:00 -07:00
12
+ date: 2009-03-11 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15