alexrevin-aasm_numerical 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.document +5 -0
  2. data/.gitignore +11 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +20 -0
  5. data/README.md +149 -0
  6. data/Rakefile +27 -0
  7. data/lib/alexrevin-aasm_numerical.rb +10 -0
  8. data/lib/alexrevin-aasm_numerical/aasm.rb +222 -0
  9. data/lib/alexrevin-aasm_numerical/event.rb +127 -0
  10. data/lib/alexrevin-aasm_numerical/localizer.rb +36 -0
  11. data/lib/alexrevin-aasm_numerical/persistence.rb +14 -0
  12. data/lib/alexrevin-aasm_numerical/persistence/active_record_persistence.rb +257 -0
  13. data/lib/alexrevin-aasm_numerical/state.rb +53 -0
  14. data/lib/alexrevin-aasm_numerical/state_machine.rb +31 -0
  15. data/lib/alexrevin-aasm_numerical/state_transition.rb +46 -0
  16. data/lib/alexrevin-aasm_numerical/supporting_classes.rb +6 -0
  17. data/lib/alexrevin-aasm_numerical/version.rb +3 -0
  18. data/spec/database.yml +3 -0
  19. data/spec/en.yml +10 -0
  20. data/spec/functional/conversation.rb +49 -0
  21. data/spec/functional/conversation_spec.rb +8 -0
  22. data/spec/schema.rb +7 -0
  23. data/spec/spec_helper.rb +16 -0
  24. data/spec/unit/aasm_spec.rb +462 -0
  25. data/spec/unit/active_record_persistence_spec.rb +246 -0
  26. data/spec/unit/before_after_callbacks_spec.rb +79 -0
  27. data/spec/unit/event_spec.rb +140 -0
  28. data/spec/unit/localizer_spec.rb +51 -0
  29. data/spec/unit/state_spec.rb +85 -0
  30. data/spec/unit/state_transition_spec.rb +163 -0
  31. data/test/functional/auth_machine_test.rb +148 -0
  32. data/test/models/process.rb +18 -0
  33. data/test/test_helper.rb +43 -0
  34. data/test/unit/aasm_test.rb +0 -0
  35. data/test/unit/event_test.rb +54 -0
  36. data/test/unit/state_machine_test.rb +37 -0
  37. data/test/unit/state_test.rb +69 -0
  38. data/test/unit/state_transition_test.rb +75 -0
  39. metadata +254 -0
@@ -0,0 +1,31 @@
1
+ class AASM::StateMachine
2
+ def self.[](clazz)
3
+ (@machines ||= {})[clazz.to_s]
4
+ end
5
+
6
+ def self.[]=(clazz, machine)
7
+ (@machines ||= {})[clazz.to_s] = machine
8
+ end
9
+
10
+ attr_accessor :states, :events, :initial_state, :config
11
+ attr_reader :name
12
+
13
+ def initialize(name)
14
+ @name = name
15
+ @initial_state = nil
16
+ @states = []
17
+ @events = {}
18
+ @config = OpenStruct.new
19
+ end
20
+
21
+ def clone
22
+ klone = super
23
+ klone.states = states.clone
24
+ klone.events = events.clone
25
+ klone
26
+ end
27
+
28
+ def create_state(name, options)
29
+ @states << AASM::SupportingClasses::State.new(name, options) unless @states.include?(name)
30
+ end
31
+ end
@@ -0,0 +1,46 @@
1
+ class AASM::SupportingClasses::StateTransition
2
+ attr_reader :from, :to, :opts
3
+ alias_method :options, :opts
4
+
5
+ def initialize(opts)
6
+ @from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
7
+ @opts = opts
8
+ end
9
+
10
+ def perform(obj, *args)
11
+ case @guard
12
+ when Symbol, String
13
+ obj.send(@guard, *args)
14
+ when Proc
15
+ @guard.call(obj, *args)
16
+ else
17
+ true
18
+ end
19
+ end
20
+
21
+ def execute(obj, *args)
22
+ @on_transition.is_a?(Array) ?
23
+ @on_transition.each {|ot| _execute(obj, ot, *args)} :
24
+ _execute(obj, @on_transition, *args)
25
+ end
26
+
27
+ def ==(obj)
28
+ @from == obj.from && @to == obj.to
29
+ end
30
+
31
+ def from?(value)
32
+ @from == value
33
+ end
34
+
35
+ private
36
+
37
+ def _execute(obj, on_transition, *args)
38
+ case on_transition
39
+ when Proc
40
+ on_transition.arity == 0 ? on_transition.call : on_transition.call(obj, *args)
41
+ when Symbol, String
42
+ obj.send(:method, on_transition.to_sym).arity == 0 ? obj.send(on_transition) : obj.send(on_transition, *args)
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,6 @@
1
+ module AASM::SupportingClasses
2
+ end
3
+
4
+ require File.join(File.dirname(__FILE__), 'state_transition')
5
+ require File.join(File.dirname(__FILE__), 'event')
6
+ require File.join(File.dirname(__FILE__), 'state')
@@ -0,0 +1,3 @@
1
+ module AASM
2
+ VERSION = "2.3.1"
3
+ end
@@ -0,0 +1,3 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: spec/aasm.sqlite3.db
@@ -0,0 +1,10 @@
1
+ en:
2
+ activerecord:
3
+ events:
4
+ localizer_test_model:
5
+ close: "Let's close it!"
6
+
7
+ attributes:
8
+ localizer_test_model:
9
+ aasm_state:
10
+ open: "It's opened now!"
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'alexrevin-aasm_numerical')
2
+
3
+ class Conversation
4
+ include AASM
5
+
6
+ aasm_initial_state :needs_attention
7
+
8
+ aasm_state :needs_attention
9
+ aasm_state :read
10
+ aasm_state :closed
11
+ aasm_state :awaiting_response
12
+ aasm_state :junk
13
+
14
+ aasm_event :new_message do
15
+ end
16
+
17
+ aasm_event :view do
18
+ transitions :to => :read, :from => [:needs_attention]
19
+ end
20
+
21
+ aasm_event :reply do
22
+ end
23
+
24
+ aasm_event :close do
25
+ transitions :to => :closed, :from => [:read, :awaiting_response]
26
+ end
27
+
28
+ aasm_event :junk do
29
+ transitions :to => :junk, :from => [:read]
30
+ end
31
+
32
+ aasm_event :unjunk do
33
+ end
34
+
35
+ def initialize(persister)
36
+ @persister = persister
37
+ end
38
+
39
+
40
+ private
41
+ def aasm_read_state
42
+ @persister.read_state
43
+ end
44
+
45
+ def aasm_write_state(state)
46
+ @persister.write_state(state)
47
+ end
48
+
49
+ end
@@ -0,0 +1,8 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), 'conversation'))
3
+
4
+ describe Conversation, 'description' do
5
+ it '.aasm_states should contain all of the states' do
6
+ Conversation.aasm_states.should == [:needs_attention, :read, :closed, :awaiting_response, :junk]
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+
3
+ %w{gates readers writers transients simples thieves localizer_test_models}.each do |table_name|
4
+ create_table table_name, :force => true
5
+ end
6
+
7
+ end
@@ -0,0 +1,16 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
2
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
3
+ require 'alexrevin-aasm_numerical'
4
+
5
+ require 'rspec'
6
+ require 'rspec/autorun'
7
+
8
+ # require 'ruby-debug'; Debugger.settings[:autoeval] = true; debugger; rubys_debugger = 'annoying'
9
+ # require 'ruby-debug/completion'
10
+
11
+ def load_schema
12
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
13
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
14
+ ActiveRecord::Base.establish_connection(config['sqlite3'])
15
+ load(File.dirname(__FILE__) + "/schema.rb")
16
+ end
@@ -0,0 +1,462 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ class Foo
4
+ include AASM
5
+ aasm_initial_state :open
6
+ aasm_state :open, :exit => :exit
7
+ aasm_state :closed, :enter => :enter
8
+
9
+ aasm_event :close, :success => :success_callback do
10
+ transitions :to => :closed, :from => [:open]
11
+ end
12
+
13
+ aasm_event :null do
14
+ transitions :to => :closed, :from => [:open], :guard => :always_false
15
+ end
16
+
17
+ def always_false
18
+ false
19
+ end
20
+
21
+ def success_callback
22
+ end
23
+
24
+ def enter
25
+ end
26
+ def exit
27
+ end
28
+ end
29
+
30
+ class FooTwo < Foo
31
+ include AASM
32
+ aasm_state :foo
33
+ end
34
+
35
+ class Bar
36
+ include AASM
37
+
38
+ aasm_state :read
39
+ aasm_state :ended
40
+
41
+ aasm_event :foo do
42
+ transitions :to => :ended, :from => [:read]
43
+ end
44
+ end
45
+
46
+ class Baz < Bar
47
+ end
48
+
49
+ class Banker
50
+ include AASM
51
+ aasm_initial_state Proc.new { |banker| banker.rich? ? :retired : :selling_bad_mortgages }
52
+ aasm_state :retired
53
+ aasm_state :selling_bad_mortgages
54
+ RICH = 1_000_000
55
+ attr_accessor :balance
56
+ def initialize(balance = 0); self.balance = balance; end
57
+ def rich?; self.balance >= RICH; end
58
+ end
59
+
60
+
61
+ describe AASM, '- class level definitions' do
62
+ it 'should define a class level aasm_initial_state() method on its including class' do
63
+ Foo.should respond_to(:aasm_initial_state)
64
+ end
65
+
66
+ it 'should define a class level aasm_state() method on its including class' do
67
+ Foo.should respond_to(:aasm_state)
68
+ end
69
+
70
+ it 'should define a class level aasm_event() method on its including class' do
71
+ Foo.should respond_to(:aasm_event)
72
+ end
73
+
74
+ it 'should define a class level aasm_states() method on its including class' do
75
+ Foo.should respond_to(:aasm_states)
76
+ end
77
+
78
+ it 'should define a class level aasm_states_for_select() method on its including class' do
79
+ Foo.should respond_to(:aasm_states_for_select)
80
+ end
81
+
82
+ it 'should define a class level aasm_events() method on its including class' do
83
+ Foo.should respond_to(:aasm_events)
84
+ end
85
+
86
+ end
87
+
88
+
89
+ describe AASM, '- subclassing' do
90
+ it 'should have the parent states' do
91
+ Foo.aasm_states.each do |state|
92
+ FooTwo.aasm_states.should include(state)
93
+ end
94
+ end
95
+
96
+ it 'should not add the child states to the parent machine' do
97
+ Foo.aasm_states.should_not include(:foo)
98
+ end
99
+ end
100
+
101
+
102
+ describe AASM, '- aasm_states_for_select' do
103
+ it "should return a select friendly array of states in the form of [['Friendly name', 'state_name']]" do
104
+ Foo.aasm_states_for_select.should == [['Open', 'open'], ['Closed', 'closed']]
105
+ end
106
+ end
107
+
108
+ describe AASM, '- instance level definitions' do
109
+ before(:each) do
110
+ @foo = Foo.new
111
+ end
112
+
113
+ it 'should define a state querying instance method on including class' do
114
+ @foo.should respond_to(:open?)
115
+ end
116
+
117
+ it 'should define an event! inance method' do
118
+ @foo.should respond_to(:close!)
119
+ end
120
+ end
121
+
122
+ describe AASM, '- initial states' do
123
+ before(:each) do
124
+ @foo = Foo.new
125
+ @bar = Bar.new
126
+ end
127
+
128
+ it 'should set the initial state' do
129
+ @foo.aasm_current_state.should == :open
130
+ end
131
+
132
+ it '#open? should be initially true' do
133
+ @foo.open?.should be_true
134
+ end
135
+
136
+ it '#closed? should be initially false' do
137
+ @foo.closed?.should be_false
138
+ end
139
+
140
+ it 'should use the first state defined if no initial state is given' do
141
+ @bar.aasm_current_state.should == :read
142
+ end
143
+
144
+ it 'should determine initial state from the Proc results' do
145
+ Banker.new(Banker::RICH - 1).aasm_current_state.should == :selling_bad_mortgages
146
+ Banker.new(Banker::RICH + 1).aasm_current_state.should == :retired
147
+ end
148
+ end
149
+
150
+ describe AASM, '- event firing with persistence' do
151
+ it 'should fire the Event' do
152
+ foo = Foo.new
153
+
154
+ Foo.aasm_events[:close].should_receive(:fire).with(foo)
155
+ foo.close!
156
+ end
157
+
158
+ it 'should update the current state' do
159
+ foo = Foo.new
160
+ foo.close!
161
+
162
+ foo.aasm_current_state.should == :closed
163
+ end
164
+
165
+ it 'should call the success callback if one was provided' do
166
+ foo = Foo.new
167
+
168
+ foo.should_receive(:success_callback)
169
+
170
+ foo.close!
171
+ end
172
+
173
+ it 'should attempt to persist if aasm_write_state is defined' do
174
+ foo = Foo.new
175
+
176
+ def foo.aasm_write_state
177
+ end
178
+
179
+ foo.should_receive(:aasm_write_state)
180
+
181
+ foo.close!
182
+ end
183
+
184
+ it 'should return true if aasm_write_state is defined and returns true' do
185
+ foo = Foo.new
186
+
187
+ def foo.aasm_write_state(state)
188
+ true
189
+ end
190
+
191
+ foo.close!.should be_true
192
+ end
193
+
194
+ it 'should return false if aasm_write_state is defined and returns false' do
195
+ foo = Foo.new
196
+
197
+ def foo.aasm_write_state(state)
198
+ false
199
+ end
200
+
201
+ foo.close!.should be_false
202
+ end
203
+
204
+ it "should not update the aasm_current_state if the write fails" do
205
+ foo = Foo.new
206
+
207
+ def foo.aasm_write_state
208
+ false
209
+ end
210
+
211
+ foo.should_receive(:aasm_write_state)
212
+
213
+ foo.close!
214
+ foo.aasm_current_state.should == :open
215
+ end
216
+ end
217
+
218
+ describe AASM, '- event firing without persistence' do
219
+ it 'should fire the Event' do
220
+ foo = Foo.new
221
+
222
+ Foo.aasm_events[:close].should_receive(:fire).with(foo)
223
+ foo.close
224
+ end
225
+
226
+ it 'should update the current state' do
227
+ foo = Foo.new
228
+ foo.close
229
+
230
+ foo.aasm_current_state.should == :closed
231
+ end
232
+
233
+ it 'should attempt to persist if aasm_write_state is defined' do
234
+ foo = Foo.new
235
+
236
+ def foo.aasm_write_state
237
+ end
238
+
239
+ foo.should_receive(:aasm_write_state_without_persistence).twice
240
+
241
+ foo.close
242
+ end
243
+ end
244
+
245
+ describe AASM, '- persistence' do
246
+ it 'should read the state if it has not been set and aasm_read_state is defined' do
247
+ foo = Foo.new
248
+ def foo.aasm_read_state
249
+ end
250
+
251
+ foo.should_receive(:aasm_read_state)
252
+
253
+ foo.aasm_current_state
254
+ end
255
+ end
256
+
257
+ describe AASM, '- getting events for a state' do
258
+ it '#aasm_events_for_current_state should use current state' do
259
+ foo = Foo.new
260
+ foo.should_receive(:aasm_current_state)
261
+ foo.aasm_events_for_current_state
262
+ end
263
+
264
+ it '#aasm_events_for_current_state should use aasm_events_for_state' do
265
+ foo = Foo.new
266
+ foo.stub!(:aasm_current_state).and_return(:foo)
267
+ foo.should_receive(:aasm_events_for_state).with(:foo)
268
+ foo.aasm_events_for_current_state
269
+ end
270
+ end
271
+
272
+ describe AASM, '- event callbacks' do
273
+ describe "with an error callback defined" do
274
+ before do
275
+ class Foo
276
+ aasm_event :safe_close, :success => :success_callback, :error => :error_callback do
277
+ transitions :to => :closed, :from => [:open]
278
+ end
279
+ end
280
+
281
+ @foo = Foo.new
282
+ end
283
+
284
+ it "should run error_callback if an exception is raised and error_callback defined" do
285
+ def @foo.error_callback(e)
286
+ end
287
+ @foo.stub!(:enter).and_raise(e=StandardError.new)
288
+ @foo.should_receive(:error_callback).with(e)
289
+ @foo.safe_close!
290
+ end
291
+
292
+ it "should raise NoMethodError if exceptionis raised and error_callback is declared but not defined" do
293
+ @foo.stub!(:enter).and_raise(StandardError)
294
+ lambda{@foo.safe_close!}.should raise_error(NoMethodError)
295
+ end
296
+
297
+ it "should propagate an error if no error callback is declared" do
298
+ @foo.stub!(:enter).and_raise("Cannot enter safe")
299
+ lambda{@foo.close!}.should raise_error(StandardError, "Cannot enter safe")
300
+ end
301
+ end
302
+
303
+ describe "with aasm_event_fired defined" do
304
+ before do
305
+ @foo = Foo.new
306
+ def @foo.aasm_event_fired(event, from, to)
307
+ end
308
+ end
309
+
310
+ it 'should call it for successful bang fire' do
311
+ @foo.should_receive(:aasm_event_fired).with(:close, :open, :closed)
312
+ @foo.close!
313
+ end
314
+
315
+ it 'should call it for successful non-bang fire' do
316
+ @foo.should_receive(:aasm_event_fired)
317
+ @foo.close
318
+ end
319
+
320
+ it 'should not call it for failing bang fire' do
321
+ @foo.stub!(:set_aasm_current_state_with_persistence).and_return(false)
322
+ @foo.should_not_receive(:aasm_event_fired)
323
+ @foo.close!
324
+ end
325
+ end
326
+
327
+ describe "with aasm_event_failed defined" do
328
+ before do
329
+ @foo = Foo.new
330
+ def @foo.aasm_event_failed(event, from)
331
+ end
332
+ end
333
+
334
+ it 'should call it when transition failed for bang fire' do
335
+ @foo.should_receive(:aasm_event_failed).with(:null, :open)
336
+ @foo.null!
337
+ end
338
+
339
+ it 'should call it when transition failed for non-bang fire' do
340
+ @foo.should_receive(:aasm_event_failed).with(:null, :open)
341
+ @foo.null
342
+ end
343
+
344
+ it 'should not call it if persist fails for bang fire' do
345
+ @foo.stub!(:set_aasm_current_state_with_persistence).and_return(false)
346
+ @foo.should_receive(:aasm_event_failed)
347
+ @foo.close!
348
+ end
349
+ end
350
+ end
351
+
352
+ describe AASM, '- state actions' do
353
+ it "should call enter when entering state" do
354
+ foo = Foo.new
355
+ foo.should_receive(:enter)
356
+
357
+ foo.close
358
+ end
359
+
360
+ it "should call exit when exiting state" do
361
+ foo = Foo.new
362
+ foo.should_receive(:exit)
363
+
364
+ foo.close
365
+ end
366
+ end
367
+
368
+
369
+ describe Baz do
370
+ it "should have the same states as it's parent" do
371
+ Baz.aasm_states.should == Bar.aasm_states
372
+ end
373
+
374
+ it "should have the same events as it's parent" do
375
+ Baz.aasm_events.should == Bar.aasm_events
376
+ end
377
+ end
378
+
379
+
380
+ class ChetanPatil
381
+ include AASM
382
+ aasm_initial_state :sleeping
383
+ aasm_state :sleeping
384
+ aasm_state :showering
385
+ aasm_state :working
386
+ aasm_state :dating
387
+ aasm_state :prettying_up
388
+
389
+ aasm_event :wakeup do
390
+ transitions :from => :sleeping, :to => [:showering, :working]
391
+ end
392
+
393
+ aasm_event :dress do
394
+ transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
395
+ transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
396
+ transitions :from => :showering, :to => :prettying_up, :on_transition => [:condition_hair, :fix_hair]
397
+ end
398
+
399
+ def wear_clothes(shirt_color, trouser_type)
400
+ end
401
+
402
+ def condition_hair
403
+ end
404
+
405
+ def fix_hair
406
+ end
407
+ end
408
+
409
+
410
+ describe ChetanPatil do
411
+ it 'should transition to specified next state (sleeping to showering)' do
412
+ cp = ChetanPatil.new
413
+ cp.wakeup! :showering
414
+
415
+ cp.aasm_current_state.should == :showering
416
+ end
417
+
418
+ it 'should transition to specified next state (sleeping to working)' do
419
+ cp = ChetanPatil.new
420
+ cp.wakeup! :working
421
+
422
+ cp.aasm_current_state.should == :working
423
+ end
424
+
425
+ it 'should transition to default (first or showering) state' do
426
+ cp = ChetanPatil.new
427
+ cp.wakeup!
428
+
429
+ cp.aasm_current_state.should == :showering
430
+ end
431
+
432
+ it 'should transition to default state when on_transition invoked' do
433
+ cp = ChetanPatil.new
434
+ cp.dress!(nil, 'purple', 'dressy')
435
+
436
+ cp.aasm_current_state.should == :working
437
+ end
438
+
439
+ it 'should call on_transition method with args' do
440
+ cp = ChetanPatil.new
441
+ cp.wakeup! :showering
442
+
443
+ cp.should_receive(:wear_clothes).with('blue', 'jeans')
444
+ cp.dress! :working, 'blue', 'jeans'
445
+ end
446
+
447
+ it 'should call on_transition proc' do
448
+ cp = ChetanPatil.new
449
+ cp.wakeup! :showering
450
+
451
+ cp.should_receive(:wear_clothes).with('purple', 'slacks')
452
+ cp.dress!(:dating, 'purple', 'slacks')
453
+ end
454
+
455
+ it 'should call on_transition with an array of methods' do
456
+ cp = ChetanPatil.new
457
+ cp.wakeup! :showering
458
+ cp.should_receive(:condition_hair)
459
+ cp.should_receive(:fix_hair)
460
+ cp.dress!(:prettying_up)
461
+ end
462
+ end