alexrevin-aasm_numerical 2.3.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.
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