ssm 0.1.8
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/lib/event.rb +22 -0
- data/lib/injection_strategies/active_record_strategy.rb +19 -0
- data/lib/injection_strategies/base.rb +6 -0
- data/lib/injection_strategies/object_strategy.rb +28 -0
- data/lib/ssm.rb +354 -0
- data/lib/state.rb +17 -0
- data/lib/state_machine.rb +123 -0
- data/lib/state_transition.rb +20 -0
- metadata +74 -0
data/lib/event.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module SSM
|
2
|
+
|
3
|
+
class Event
|
4
|
+
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :block
|
7
|
+
attr_reader :transition
|
8
|
+
|
9
|
+
def initialize(name, transition=nil, &block) #:nodoc:
|
10
|
+
@name, @transition = name, transition
|
11
|
+
@block = block if block
|
12
|
+
end
|
13
|
+
|
14
|
+
# Compares this Event with another Event and returns true if
|
15
|
+
# either the Event is the same or the name of the Event is the same.
|
16
|
+
def equal(event)
|
17
|
+
self === event || @name === event.name
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SSM
|
2
|
+
module InjectionStrategies
|
3
|
+
module ActiveRecordStrategy
|
4
|
+
|
5
|
+
def ssm_setup
|
6
|
+
_synchronize_state if new_record?
|
7
|
+
end
|
8
|
+
|
9
|
+
def ssm_set(v)
|
10
|
+
send("#{@ssm_state_machine.property_name}=".to_sym, v)
|
11
|
+
end
|
12
|
+
|
13
|
+
def ssm_get
|
14
|
+
send("#{@ssm_state_machine.property_name}".to_sym)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SSM
|
2
|
+
module InjectionStrategies
|
3
|
+
module ObjectStrategy
|
4
|
+
|
5
|
+
# Generic setup
|
6
|
+
def ssm_setup
|
7
|
+
sm = @ssm_state_machine
|
8
|
+
unless sm.property_name.nil?
|
9
|
+
# This allows others to set up the object however they see fit, including mixing in setters.
|
10
|
+
instance_eval("def #{sm.property_name}; @#{sm.property_name}; end") unless respond_to?(sm.property_name)
|
11
|
+
instance_eval("def #{sm.property_name}=(v); @#{sm.property_name} = v; end") unless respond_to?("#{sm.property_name}=".to_sym)
|
12
|
+
|
13
|
+
_synchronize_state
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def ssm_set(v)
|
19
|
+
send("#{@ssm_state_machine.property_name}=".to_sym, v)
|
20
|
+
end
|
21
|
+
|
22
|
+
def ssm_get
|
23
|
+
send("#{@ssm_state_machine.property_name}".to_sym)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/ssm.rb
ADDED
@@ -0,0 +1,354 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'state_machine')
|
2
|
+
require File.join(File.dirname(__FILE__), 'injection_strategies', 'base')
|
3
|
+
|
4
|
+
# SSM - Simple State Machine mixin
|
5
|
+
#
|
6
|
+
# SSM is a mixin that adds finite-state machine behavior to a class.
|
7
|
+
#
|
8
|
+
# Example usage:
|
9
|
+
#
|
10
|
+
# class Door
|
11
|
+
# include SSM
|
12
|
+
#
|
13
|
+
# ssm_initial_state :closed
|
14
|
+
# ssm_state :opened
|
15
|
+
#
|
16
|
+
# ssm_event :open, :from => [:closed], :to => :opened do
|
17
|
+
# puts "Just opened door"
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# ssm_event :close, :from => [:opened], :to => :closed do
|
21
|
+
# puts "Closed door"
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# door = Door.new
|
27
|
+
# door.open
|
28
|
+
# door.is?(:opened) #=> true
|
29
|
+
# door.close
|
30
|
+
#--
|
31
|
+
# Including SSM ensures that both class-level and instance-level methods are
|
32
|
+
# available (through the use of extend on self.included).
|
33
|
+
#
|
34
|
+
# Each include on a class creates a new StateMachine template for the class it is being
|
35
|
+
# included in. The meta methods will then help build that template StateMachine.
|
36
|
+
#
|
37
|
+
# Each time an instance of the Foo class is created, the template StateMachine is cloned,
|
38
|
+
# and attached to the new instance. The cloned StateMachine can not be manipulated at runtime.
|
39
|
+
# This ensures that as long as a class is not modified at runtime, all its instances' StateMachines
|
40
|
+
# are equivalent.
|
41
|
+
#--
|
42
|
+
module SSM
|
43
|
+
|
44
|
+
VERSION = '0.1.7';
|
45
|
+
|
46
|
+
class InvalidTransition < RuntimeError; end
|
47
|
+
class UndefinedState < RuntimeError; end
|
48
|
+
class DuplicateState < RuntimeError; end
|
49
|
+
class UndefinedEvent < RuntimeError; end
|
50
|
+
class DuplicateEvent < RuntimeError; end
|
51
|
+
class InitialStateRequired < RuntimeError; end
|
52
|
+
|
53
|
+
# TemplateStateMachines stores the StateMachine templates for each class
|
54
|
+
# that includes SSM. Because all setup is done before instantiation, each
|
55
|
+
# instance will then have a consistent StateMachine.
|
56
|
+
TemplateStateMachines = {} #:nodoc:
|
57
|
+
|
58
|
+
# First, extend the class with static methods as soon as the module is mixed in.
|
59
|
+
# Then, initialize the StateMachine when the module is mixed in. This allows the meta
|
60
|
+
# calls to build the StateMachine before instantiation, and once the model is actually
|
61
|
+
# instanciated, store a copy of the StateMachine.
|
62
|
+
#
|
63
|
+
# Note: We use the actual Class as the key to the TemplateStateMachines hash. If the Class is redeclared,
|
64
|
+
# the hash will see it as a new key, even though if you inspect the hash you will see two keys whose string
|
65
|
+
# representation is the same.
|
66
|
+
def self.included(klass) #:nodoc:
|
67
|
+
|
68
|
+
klass.extend SSM::ClassMethods
|
69
|
+
SSM::TemplateStateMachines[klass] = SSM::StateMachine.new
|
70
|
+
|
71
|
+
def klass.allocate(*args)
|
72
|
+
instance = super(*args)
|
73
|
+
setup(instance)
|
74
|
+
instance
|
75
|
+
end
|
76
|
+
|
77
|
+
# Intercept contructor. We can't overide initialize given that the user
|
78
|
+
# may define their own initialize method.
|
79
|
+
def klass.new(*args)
|
80
|
+
instance = super(*args)
|
81
|
+
setup(instance)
|
82
|
+
instance
|
83
|
+
end
|
84
|
+
|
85
|
+
def klass.setup(instance)
|
86
|
+
|
87
|
+
SSM::TemplateStateMachines[self].validate
|
88
|
+
|
89
|
+
sm = SSM::TemplateStateMachines[self].clone_and_freeze
|
90
|
+
instance.instance_variable_set(:@ssm_state_machine, sm)
|
91
|
+
|
92
|
+
begin
|
93
|
+
strategy_name = sm.injection_strategy.nil? ? "object" : sm.injection_strategy.to_s
|
94
|
+
module_name = "#{strategy_name.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }}Strategy" # from ActiveSupport
|
95
|
+
require File.join(File.dirname(__FILE__), 'injection_strategies', "#{strategy_name}_strategy.rb")
|
96
|
+
instance.extend(SSM::InjectionStrategies.const_get(module_name))
|
97
|
+
rescue
|
98
|
+
raise
|
99
|
+
end
|
100
|
+
|
101
|
+
instance.ssm_setup
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
# Class methods to be mixed in when the module is included.
|
108
|
+
#
|
109
|
+
#--
|
110
|
+
# Note: self returns the actual class
|
111
|
+
#--
|
112
|
+
module ClassMethods
|
113
|
+
|
114
|
+
attr_accessor :ssm_instance_property
|
115
|
+
|
116
|
+
def inherited(subclass) #:nodoc:
|
117
|
+
raise Exception.new("SSM cannot be inherited. Use include instead.")
|
118
|
+
end
|
119
|
+
|
120
|
+
# This method is used as both a setter - in the context of the class declaration -
|
121
|
+
# and a getter when called from an instance.
|
122
|
+
#
|
123
|
+
# class Door
|
124
|
+
# include SSM
|
125
|
+
#
|
126
|
+
# ssm_initial_state :closed
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# Door.new.ssm_initial_state #=> :closed
|
130
|
+
#
|
131
|
+
def ssm_initial_state(name=nil)
|
132
|
+
name.nil? ?
|
133
|
+
SSM::TemplateStateMachines[self].initial_state :
|
134
|
+
SSM::TemplateStateMachines[self].initial_state = SSM::State.new(name)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Sets the instance attribute that stores a representation of the State. In
|
138
|
+
# the first form, the property will return a symbol represeting the State.
|
139
|
+
# In the second form, an integer is returned, making it more convenient when
|
140
|
+
# dealing with persistence.
|
141
|
+
#
|
142
|
+
# class Door
|
143
|
+
# include SSM
|
144
|
+
#
|
145
|
+
# ssm_inject_state_into :state
|
146
|
+
# ssm_initial_state :closed
|
147
|
+
# end
|
148
|
+
#
|
149
|
+
# Door.new.state #=> :closed
|
150
|
+
#
|
151
|
+
#
|
152
|
+
# class Door
|
153
|
+
# include SSM
|
154
|
+
#
|
155
|
+
# ssm_inject_state_into :state, :as_integer => true
|
156
|
+
# ssm_initial_state :closed
|
157
|
+
# end
|
158
|
+
#
|
159
|
+
# Door.new.state #=> 0
|
160
|
+
#
|
161
|
+
def ssm_inject_state_into(name, options={}, &block)
|
162
|
+
SSM::TemplateStateMachines[self].property_name = name
|
163
|
+
SSM::TemplateStateMachines[self].use_property_index = options[:as_integer].nil? ? false : true
|
164
|
+
SSM::TemplateStateMachines[self].injection_strategy = options[:strategy] #SSM::InjectionStrategies::Base.factory(options[:strategy])
|
165
|
+
end
|
166
|
+
|
167
|
+
# Adds new States. This method takes a string or a symbol.
|
168
|
+
#
|
169
|
+
# class Door
|
170
|
+
# include SSM
|
171
|
+
#
|
172
|
+
# ssm_state :closed
|
173
|
+
# ssm_state :opened
|
174
|
+
# end
|
175
|
+
#
|
176
|
+
def ssm_state(name, options={})
|
177
|
+
SSM::TemplateStateMachines[self] << SSM::State.new(name)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Adds new Events. These Events can then be called as methods.
|
181
|
+
#
|
182
|
+
# class Door
|
183
|
+
# include SSM
|
184
|
+
#
|
185
|
+
# ssm_initial_state :closed
|
186
|
+
# ssm_state :opened
|
187
|
+
#
|
188
|
+
# ssm_event :open, :from => [:closed], :to => :opened do
|
189
|
+
# puts "Just opened door"
|
190
|
+
# end
|
191
|
+
#
|
192
|
+
# ssm_event :close, :from => [:opened], :to => :closed do
|
193
|
+
# puts "Closed door"
|
194
|
+
# end
|
195
|
+
#
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# door = Door.new
|
199
|
+
# door.open
|
200
|
+
# door.is?(:opened) #=> true
|
201
|
+
# door.closed
|
202
|
+
#
|
203
|
+
def ssm_event(name, options = {}, &block)
|
204
|
+
|
205
|
+
msg = "Please specificy a final state for this transition. Use a lowly instance method if a transition is not required."
|
206
|
+
raise SSM::InvalidTransition.new(msg) unless options[:to].is_a?(Symbol)
|
207
|
+
|
208
|
+
begin
|
209
|
+
# build Array of States this transition can be called from
|
210
|
+
from = []
|
211
|
+
if options[:from].is_a?(Array) and options[:from].size > 0
|
212
|
+
options[:from].each { |state_name| from << SSM::TemplateStateMachines[self].get_state_by_name(state_name) }
|
213
|
+
end
|
214
|
+
|
215
|
+
to = SSM::TemplateStateMachines[self].get_state_by_name(options[:to])
|
216
|
+
rescue
|
217
|
+
raise
|
218
|
+
end
|
219
|
+
|
220
|
+
# Create StateMachine and create method associated with this StateTransition
|
221
|
+
SSM::TemplateStateMachines[self] << SSM::Event.new(name, SSM::StateTransition.new(from, to), &block)
|
222
|
+
define_method("#{name.to_s}") { |*args| _synchronize_state; _ssm_trigger_event(name, args) }
|
223
|
+
end
|
224
|
+
|
225
|
+
def template_state_machine #:nodoc:
|
226
|
+
SSM::TemplateStateMachines[self]
|
227
|
+
end
|
228
|
+
|
229
|
+
# Returns all the available Events
|
230
|
+
def ssm_events
|
231
|
+
template_state_machine.events
|
232
|
+
end
|
233
|
+
|
234
|
+
# Returns all the available States
|
235
|
+
def ssm_states
|
236
|
+
template_state_machine.states
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
#
|
242
|
+
# instance methods
|
243
|
+
#
|
244
|
+
attr_reader :ssm_state_machine
|
245
|
+
|
246
|
+
# Returns true if the Object is in the State represented by the name or symbol.
|
247
|
+
#
|
248
|
+
# class Door
|
249
|
+
# include SSM
|
250
|
+
#
|
251
|
+
# ssm_initial_state :closed
|
252
|
+
# ssm_state :opened
|
253
|
+
#
|
254
|
+
# ssm_event :open, :from => [:closed], :to => :opened do
|
255
|
+
# puts "Just opened door"
|
256
|
+
# end
|
257
|
+
#
|
258
|
+
# ssm_event :close, :from => [:opened], :to => :closed do
|
259
|
+
# puts "Closed door"
|
260
|
+
# end
|
261
|
+
#
|
262
|
+
# end
|
263
|
+
#
|
264
|
+
# door = Door.new
|
265
|
+
# door.open
|
266
|
+
# door.is?(:opened) #=> true
|
267
|
+
#
|
268
|
+
def is?(state_name_or_symbol)
|
269
|
+
_synchronize_state
|
270
|
+
@ssm_state_machine.current_state.name.to_sym == state_name_or_symbol.to_sym
|
271
|
+
end
|
272
|
+
|
273
|
+
# Returns true if the Object is not in the State represented by the name or symbol.
|
274
|
+
#
|
275
|
+
# class Door
|
276
|
+
# include SSM
|
277
|
+
#
|
278
|
+
# ssm_initial_state :closed
|
279
|
+
# ssm_state :opened
|
280
|
+
#
|
281
|
+
# ssm_event :open, :from => [:closed], :to => :opened do
|
282
|
+
# puts "Just opened door"
|
283
|
+
# end
|
284
|
+
#
|
285
|
+
# ssm_event :close, :from => [:opened], :to => :closed do
|
286
|
+
# puts "Closed door"
|
287
|
+
# end
|
288
|
+
#
|
289
|
+
# end
|
290
|
+
#
|
291
|
+
# door = Door.new
|
292
|
+
# door.open
|
293
|
+
# door.is?(:closed) #=> false
|
294
|
+
#
|
295
|
+
def is_not?(state_name_or_symbol)
|
296
|
+
_synchronize_state
|
297
|
+
@ssm_state_machine.current_state.name.to_sym != state_name_or_symbol.to_sym
|
298
|
+
end
|
299
|
+
|
300
|
+
# Returns a symbol representing the current State
|
301
|
+
#
|
302
|
+
# door = Door.new
|
303
|
+
# door.ssm_state #=> :closed
|
304
|
+
#
|
305
|
+
def ssm_state
|
306
|
+
_synchronize_state
|
307
|
+
@ssm_state_machine.current_state.name
|
308
|
+
end
|
309
|
+
|
310
|
+
private
|
311
|
+
|
312
|
+
def _ssm_trigger_event(event_name_or_symbol, args)
|
313
|
+
_synchronize_state
|
314
|
+
event = @ssm_state_machine.get_event_by_name(event_name_or_symbol)
|
315
|
+
|
316
|
+
@ssm_state_machine.transition(event.transition)
|
317
|
+
ssm_set(@ssm_state_machine.get_state_for_property) unless @ssm_state_machine.property_name.nil?
|
318
|
+
instance_exec *args, &event.block
|
319
|
+
end
|
320
|
+
|
321
|
+
# instance_exec for 1.8.x
|
322
|
+
# http://groups.google.com/group/ruby-talk-google/browse_thread/thread/34bc4c9b2cac3424
|
323
|
+
unless instance_methods.include? 'instance_exec' #:nodoc:
|
324
|
+
module InstanceExecHelper; end
|
325
|
+
include InstanceExecHelper
|
326
|
+
def instance_exec(*args, &block)
|
327
|
+
mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}"
|
328
|
+
InstanceExecHelper.module_eval{ define_method(mname, &block) }
|
329
|
+
begin
|
330
|
+
ret = send(mname, *args)
|
331
|
+
ensure
|
332
|
+
InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
|
333
|
+
end
|
334
|
+
ret
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def _synchronize_state
|
339
|
+
ssm_get.nil? ? ssm_set(@ssm_state_machine.get_state_for_property) : _update_ssm_state unless _state_up_to_date?
|
340
|
+
true
|
341
|
+
end
|
342
|
+
|
343
|
+
# Checks whether the StateMachine and the property in the instance are in sync
|
344
|
+
def _state_up_to_date?
|
345
|
+
@ssm_state_machine.property_name.nil? ? true : ssm_get == @ssm_state_machine.get_state_for_property
|
346
|
+
end
|
347
|
+
|
348
|
+
# Updates the StateMachine based on the value of the state property of the instance
|
349
|
+
def _update_ssm_state
|
350
|
+
unless @ssm_state_machine.property_name.nil?
|
351
|
+
@ssm_state_machine.current_state = @ssm_state_machine.use_property_index == true ? @ssm_state_machine.get_state_by_index(ssm_get) : @ssm_state_machine.get_state_by_name(ssm_get)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
data/lib/state.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'state')
|
2
|
+
require File.join(File.dirname(__FILE__), 'state_transition')
|
3
|
+
require File.join(File.dirname(__FILE__), 'event')
|
4
|
+
|
5
|
+
module SSM
|
6
|
+
|
7
|
+
class StateMachine
|
8
|
+
|
9
|
+
attr_reader :initial_state
|
10
|
+
attr_accessor :current_state
|
11
|
+
|
12
|
+
attr_accessor :property_name
|
13
|
+
attr_accessor :use_property_index
|
14
|
+
attr_accessor :injection_strategy
|
15
|
+
|
16
|
+
attr_reader :states
|
17
|
+
attr_reader :events
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@states = []
|
21
|
+
@events = []
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate
|
25
|
+
raise SSM::InitialStateRequired if @initial_state.nil?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
# Cloning is sufficent given that instances of SSM::State and SSM::Event are immutable.
|
30
|
+
# Furthermore, we freeze the Arrays storing those instances
|
31
|
+
def clone_and_freeze
|
32
|
+
clone = self.clone
|
33
|
+
clone.init
|
34
|
+
clone.freeze
|
35
|
+
clone
|
36
|
+
end
|
37
|
+
|
38
|
+
def init
|
39
|
+
@current_state = @initial_state
|
40
|
+
end
|
41
|
+
|
42
|
+
def freeze
|
43
|
+
@states.freeze
|
44
|
+
@events.freeze
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def initial_state=(state)
|
49
|
+
self << state
|
50
|
+
@initial_state = state
|
51
|
+
end
|
52
|
+
|
53
|
+
def << state_or_event
|
54
|
+
|
55
|
+
if state_or_event.is_a?(SSM::State)
|
56
|
+
push_state(state_or_event)
|
57
|
+
elsif state_or_event.is_a?(SSM::Event)
|
58
|
+
push_event(state_or_event)
|
59
|
+
else
|
60
|
+
raise TypeError
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def state_exists?(state_to_compare)
|
65
|
+
@states.find { |existing_state| existing_state.equal(state_to_compare) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def event_exists?(event_to_compare)
|
69
|
+
@events.find { |existing_event| existing_event.equal(event_to_compare) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_state_for_property
|
73
|
+
@use_property_index == true ? get_state_index_by_name(@current_state.name) : @current_state.name.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_state_by_name(name)
|
77
|
+
state = @states.find { |state| state.name == name.to_sym}
|
78
|
+
raise SSM::UndefinedState.new unless state.is_a?(SSM::State)
|
79
|
+
state
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_state_index_by_name(name)
|
83
|
+
@states.index(get_state_by_name(name))
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_state_by_index(index)
|
87
|
+
@states[index.to_i]
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_event_by_name(name)
|
91
|
+
event = @events.find { |event| event.name == name}
|
92
|
+
raise SSM::UndefinedEvent.new unless event.is_a?(SSM::Event)
|
93
|
+
event
|
94
|
+
end
|
95
|
+
|
96
|
+
def transition(transition)
|
97
|
+
begin
|
98
|
+
transition.validate(@current_state)
|
99
|
+
@current_state = transition.to
|
100
|
+
rescue SSM::InvalidTransition => e
|
101
|
+
raise e
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def push_state(new_state)
|
108
|
+
raise DuplicateState.new if state_exists?(new_state)
|
109
|
+
@states << new_state
|
110
|
+
end
|
111
|
+
|
112
|
+
def push_event(new_event)
|
113
|
+
raise DuplicateEvent.new if event_exists?(new_event)
|
114
|
+
@events << new_event
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module SSM
|
2
|
+
|
3
|
+
class StateTransition
|
4
|
+
|
5
|
+
attr_reader :from, :to
|
6
|
+
|
7
|
+
def initialize(from, to)
|
8
|
+
@from, @to = from, to
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate(current_state)
|
12
|
+
return true if @to.nil?
|
13
|
+
return true if @from.size == 0
|
14
|
+
raise SSM::InvalidTransition unless @from.find {|state| state.equal(current_state)}
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ssm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 11
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 8
|
10
|
+
version: 0.1.8
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Luis Correa d'Almeida
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-07 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: simple state machine is a mixin that adds finite-state machine behavior to a class.
|
23
|
+
email: luis.ca@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- lib/event.rb
|
32
|
+
- lib/ssm.rb
|
33
|
+
- lib/state.rb
|
34
|
+
- lib/state_machine.rb
|
35
|
+
- lib/state_transition.rb
|
36
|
+
- lib/injection_strategies/base.rb
|
37
|
+
- lib/injection_strategies/object_strategy.rb
|
38
|
+
- lib/injection_strategies/active_record_strategy.rb
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://github.com/spoonsix/ssm
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
hash: 3
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.6.2
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: simple state machine is a mixin that adds finite-state machine behavior to a class.
|
73
|
+
test_files: []
|
74
|
+
|