state_machines-activemodel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.idea/.rakeTasks +7 -0
  4. data/.idea/cssxfire.xml +9 -0
  5. data/.idea/encodings.xml +5 -0
  6. data/.idea/misc.xml +5 -0
  7. data/.idea/modules.xml +9 -0
  8. data/.idea/scopes/scope_settings.xml +5 -0
  9. data/.idea/state_machine2_activemodel.iml +32 -0
  10. data/.idea/vcs.xml +7 -0
  11. data/.idea/workspace.xml +50 -0
  12. data/.rspec +3 -0
  13. data/.travis.yml +19 -0
  14. data/Appraisals +34 -0
  15. data/Gemfile +4 -0
  16. data/LICENSE.txt +23 -0
  17. data/README.md +86 -0
  18. data/Rakefile +10 -0
  19. data/gemfiles/active_model_3.2.gemfile +7 -0
  20. data/gemfiles/active_model_3.2.gemfile.lock +50 -0
  21. data/gemfiles/active_model_4.0.gemfile +7 -0
  22. data/gemfiles/active_model_4.0.gemfile.lock +56 -0
  23. data/gemfiles/active_model_4.0_obs.gemfile +8 -0
  24. data/gemfiles/active_model_4.0_obs.gemfile.lock +59 -0
  25. data/gemfiles/active_model_4.1.gemfile +7 -0
  26. data/gemfiles/active_model_4.1.gemfile.lock +57 -0
  27. data/gemfiles/active_model_4.1_obs.gemfile +8 -0
  28. data/gemfiles/active_model_4.1_obs.gemfile.lock +60 -0
  29. data/gemfiles/active_model_edge.gemfile +7 -0
  30. data/gemfiles/active_model_edge.gemfile.lock +62 -0
  31. data/gemfiles/active_model_edge_obs.gemfile +8 -0
  32. data/gemfiles/active_model_edge_obs.gemfile.lock +65 -0
  33. data/lib/state_machines-activemodel.rb +1 -0
  34. data/lib/state_machines/integrations/active_model.rb +593 -0
  35. data/lib/state_machines/integrations/active_model/locale.rb +11 -0
  36. data/lib/state_machines/integrations/active_model/observer.rb +33 -0
  37. data/lib/state_machines/integrations/active_model/observer_update.rb +42 -0
  38. data/lib/state_machines/integrations/version.rb +7 -0
  39. data/spec/active_model_spec.rb +639 -0
  40. data/spec/integration_spec.rb +27 -0
  41. data/spec/observer_spec.rb +519 -0
  42. data/spec/spec_helper.rb +8 -0
  43. data/spec/support/en.yml +5 -0
  44. data/spec/support/helpers.rb +64 -0
  45. data/spec/support/migration_helpers.rb +43 -0
  46. data/state_machines-activemodel.gemspec +27 -0
  47. metadata +182 -0
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe StateMachines::Integrations::ActiveModel do
4
+ it { expect(StateMachines::Integrations::ActiveModel.integration_name).to eq(:active_model) }
5
+
6
+ it 'should_be_available' do
7
+ expect(StateMachines::Integrations::ActiveModel.available?).to be_truthy
8
+ end
9
+
10
+ if defined?(ActiveModel::Observing)
11
+ it 'should_match_if_class_includes_observing_feature' do
12
+ expect(StateMachines::Integrations::ActiveModel.matches?(new_model { include ActiveModel::Observing })).to be_truthy
13
+ end
14
+ end
15
+
16
+ it 'should_match_if_class_includes_validations_feature' do
17
+ expect(StateMachines::Integrations::ActiveModel.matches?(new_model { include ActiveModel::Validations })).to be_truthy
18
+ end
19
+
20
+ it 'should_not_match_if_class_does_not_include_active_model_features' do
21
+ expect(StateMachines::Integrations::ActiveModel.matches?(new_model)).to be_falsy
22
+ end
23
+
24
+ it 'should_have_no_defaults' do
25
+ expect(StateMachines::Integrations::ActiveModel.defaults).to eq({})
26
+ end
27
+ end
@@ -0,0 +1,519 @@
1
+ if ActiveModel::VERSION::MAJOR >= 4
2
+ begin
3
+ require 'rails/observers/active_model'
4
+ # require 'active_model/mass_assignment_security'
5
+ rescue LoadError
6
+ end
7
+ else
8
+ require 'active_model/observing'
9
+ end
10
+ require 'active_support/all'
11
+
12
+ if defined?(ActiveModel::Observing)
13
+
14
+ context 'ObserverUpdate' do
15
+ before(:each) do
16
+ @model = new_model { include ActiveModel::Observing }
17
+ @machine = StateMachines::Machine.new(@model)
18
+ @machine.state :parked, :idling
19
+ @machine.event :ignite
20
+
21
+ @record = @model.new(:state => 'parked')
22
+ @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling)
23
+
24
+ @observer_update = StateMachines::Integrations::ActiveModel::ObserverUpdate.new(:before_transition, @record, @transition)
25
+ end
26
+
27
+ it 'should_have_method' do
28
+ assert_equal :before_transition, @observer_update.method
29
+ end
30
+
31
+ it 'should_have_object' do
32
+ assert_equal @record, @observer_update.object
33
+ end
34
+
35
+ it 'should_have_transition' do
36
+ assert_equal @transition, @observer_update.transition
37
+ end
38
+
39
+ it 'should_include_object_and_transition_in_args' do
40
+ assert_equal [@record, @transition], @observer_update.args
41
+ end
42
+
43
+ it 'should_use_record_class_as_class' do
44
+ assert_equal @model, @observer_update.class
45
+ end
46
+ end
47
+
48
+ context 'WithObservers' do
49
+ before(:each) do
50
+ @model = new_model { include ActiveModel::Observing }
51
+ @machine = StateMachines::Machine.new(@model)
52
+ @machine.state :parked, :idling
53
+ @machine.event :ignite
54
+ @record = @model.new(:state => 'parked')
55
+ @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling)
56
+ end
57
+
58
+ it 'should_call_all_transition_callback_permutations' do
59
+ callbacks = [
60
+ :before_ignite_from_parked_to_idling,
61
+ :before_ignite_from_parked,
62
+ :before_ignite_to_idling,
63
+ :before_ignite,
64
+ :before_transition_state_from_parked_to_idling,
65
+ :before_transition_state_from_parked,
66
+ :before_transition_state_to_idling,
67
+ :before_transition_state,
68
+ :before_transition
69
+ ]
70
+
71
+ observer = new_observer(@model) do
72
+ callbacks.each do |callback|
73
+ define_method(callback) do |*args|
74
+ notifications << callback
75
+ end
76
+ end
77
+ end
78
+
79
+ instance = observer.instance
80
+
81
+ @transition.perform
82
+ assert_equal callbacks, instance.notifications
83
+ end
84
+
85
+ it 'should_call_no_transition_callbacks_when_observers_disabled' do
86
+ callbacks = [
87
+ :before_ignite,
88
+ :before_transition
89
+ ]
90
+
91
+ observer = new_observer(@model) do
92
+ callbacks.each do |callback|
93
+ define_method(callback) do |*args|
94
+ notifications << callback
95
+ end
96
+ end
97
+ end
98
+
99
+ instance = observer.instance
100
+
101
+ @model.observers.disable(observer) do
102
+ @transition.perform
103
+ end
104
+
105
+ assert_equal [], instance.notifications
106
+ end
107
+
108
+ it 'should_pass_record_and_transition_to_before_callbacks' do
109
+ observer = new_observer(@model) do
110
+ def before_transition(*args)
111
+ notifications << args
112
+ end
113
+ end
114
+ instance = observer.instance
115
+
116
+ @transition.perform
117
+ assert_equal [[@record, @transition]], instance.notifications
118
+ end
119
+
120
+ it 'should_pass_record_and_transition_to_after_callbacks' do
121
+ observer = new_observer(@model) do
122
+ def after_transition(*args)
123
+ notifications << args
124
+ end
125
+ end
126
+ instance = observer.instance
127
+
128
+ @transition.perform
129
+ assert_equal [[@record, @transition]], instance.notifications
130
+ end
131
+
132
+ it 'should_call_methods_outside_the_context_of_the_record' do
133
+ observer = new_observer(@model) do
134
+ def before_ignite(*args)
135
+ notifications << self
136
+ end
137
+ end
138
+ instance = observer.instance
139
+
140
+ @transition.perform
141
+ assert_equal [instance], instance.notifications
142
+ end
143
+
144
+ it 'should_support_nil_from_states' do
145
+ callbacks = [
146
+ :before_ignite_from_nil_to_idling,
147
+ :before_ignite_from_nil,
148
+ :before_transition_state_from_nil_to_idling,
149
+ :before_transition_state_from_nil
150
+ ]
151
+
152
+ observer = new_observer(@model) do
153
+ callbacks.each do |callback|
154
+ define_method(callback) do |*args|
155
+ notifications << callback
156
+ end
157
+ end
158
+ end
159
+
160
+ instance = observer.instance
161
+
162
+ transition = StateMachines::Transition.new(@record, @machine, :ignite, nil, :idling)
163
+ transition.perform
164
+ assert_equal callbacks, instance.notifications
165
+ end
166
+
167
+ it 'should_support_nil_to_states' do
168
+ callbacks = [
169
+ :before_ignite_from_parked_to_nil,
170
+ :before_ignite_to_nil,
171
+ :before_transition_state_from_parked_to_nil,
172
+ :before_transition_state_to_nil
173
+ ]
174
+
175
+ observer = new_observer(@model) do
176
+ callbacks.each do |callback|
177
+ define_method(callback) do |*args|
178
+ notifications << callback
179
+ end
180
+ end
181
+ end
182
+
183
+ instance = observer.instance
184
+
185
+ transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, nil)
186
+ transition.perform
187
+ assert_equal callbacks, instance.notifications
188
+ end
189
+ end
190
+
191
+ context 'WithNamespacedObservers' do
192
+ before(:each) do
193
+ @model = new_model { include ActiveModel::Observing }
194
+ @machine = StateMachines::Machine.new(@model, :state, :namespace => 'alarm')
195
+ @machine.state :active, :off
196
+ @machine.event :enable
197
+ @record = @model.new(:state => 'off')
198
+ @transition = StateMachines::Transition.new(@record, @machine, :enable, :off, :active)
199
+ end
200
+
201
+ it 'should_call_namespaced_before_event_method' do
202
+ observer = new_observer(@model) do
203
+ def before_enable_alarm(*args)
204
+ notifications << args
205
+ end
206
+ end
207
+ instance = observer.instance
208
+
209
+ @transition.perform
210
+ assert_equal [[@record, @transition]], instance.notifications
211
+ end
212
+
213
+ it 'should_call_namespaced_after_event_method' do
214
+ observer = new_observer(@model) do
215
+ def after_enable_alarm(*args)
216
+ notifications << args
217
+ end
218
+ end
219
+ instance = observer.instance
220
+
221
+ @transition.perform
222
+ assert_equal [[@record, @transition]], instance.notifications
223
+ end
224
+ end
225
+
226
+ context 'WithFailureCallbacks' do
227
+ before(:each) do
228
+ @model = new_model { include ActiveModel::Observing }
229
+ @machine = StateMachines::Machine.new(@model)
230
+ @machine.state :parked, :idling
231
+ @machine.event :ignite
232
+ @record = @model.new(:state => 'parked')
233
+ @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling)
234
+
235
+ @notifications = []
236
+
237
+ # Create callbacks
238
+ @machine.before_transition { false }
239
+ @machine.after_failure { @notifications << :callback_after_failure }
240
+
241
+ # Create observer callbacks
242
+ observer = new_observer(@model) do
243
+ def after_failure_to_ignite(*args)
244
+ notifications << :observer_after_failure_ignite
245
+ end
246
+
247
+ def after_failure_to_transition(*args)
248
+ notifications << :observer_after_failure_transition
249
+ end
250
+ end
251
+ instance = observer.instance
252
+ instance.notifications = @notifications
253
+
254
+ @transition.perform
255
+ end
256
+
257
+ it 'should_invoke_callbacks_in_specific_order' do
258
+ expected = [
259
+ :callback_after_failure,
260
+ :observer_after_failure_ignite,
261
+ :observer_after_failure_transition
262
+ ]
263
+
264
+ assert_equal expected, @notifications
265
+ end
266
+ end
267
+
268
+ context 'WithMixedCallbacks' do
269
+ before(:each) do
270
+ @model = new_model { include ActiveModel::Observing }
271
+ @machine = StateMachines::Machine.new(@model)
272
+ @machine.state :parked, :idling
273
+ @machine.event :ignite
274
+ @record = @model.new(:state => 'parked')
275
+ @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling)
276
+
277
+ @notifications = []
278
+
279
+ # Create callbacks
280
+ @machine.before_transition { @notifications << :callback_before_transition }
281
+ @machine.after_transition { @notifications << :callback_after_transition }
282
+ @machine.around_transition { |block| @notifications << :callback_around_before_transition; block.call; @notifications << :callback_around_after_transition }
283
+
284
+ # Create observer callbacks
285
+ observer = new_observer(@model) do
286
+ def before_ignite(*args)
287
+ notifications << :observer_before_ignite
288
+ end
289
+
290
+ def before_transition(*args)
291
+ notifications << :observer_before_transition
292
+ end
293
+
294
+ def after_ignite(*args)
295
+ notifications << :observer_after_ignite
296
+ end
297
+
298
+ def after_transition(*args)
299
+ notifications << :observer_after_transition
300
+ end
301
+ end
302
+ instance = observer.instance
303
+ instance.notifications = @notifications
304
+
305
+ @transition.perform
306
+ end
307
+
308
+ it 'should_invoke_callbacks_in_specific_order' do
309
+ expected = [
310
+ :callback_before_transition,
311
+ :callback_around_before_transition,
312
+ :observer_before_ignite,
313
+ :observer_before_transition,
314
+ :callback_around_after_transition,
315
+ :callback_after_transition,
316
+ :observer_after_ignite,
317
+ :observer_after_transition
318
+ ]
319
+
320
+ assert_equal expected, @notifications
321
+ end
322
+ end
323
+
324
+ context 'WithInternationalization' do
325
+ before(:each) do
326
+ I18n.backend = I18n::Backend::Simple.new
327
+
328
+ # Initialize the backend
329
+ I18n.backend.translate(:en, 'activemodel.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling')
330
+
331
+ @model = new_model { include ActiveModel::Validations }
332
+ end
333
+
334
+ it 'should_use_defaults' do
335
+ I18n.backend.store_translations(:en, {
336
+ :activemodel => {:errors => {:messages => {:invalid_transition => 'cannot %{event}'}}}
337
+ })
338
+
339
+ machine = StateMachines::Machine.new(@model, :action => :save)
340
+ machine.state :parked, :idling
341
+ machine.event :ignite
342
+
343
+ record = @model.new(:state => 'idling')
344
+
345
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
346
+ assert_equal ['State cannot ignite'], record.errors.full_messages
347
+ end
348
+
349
+ it 'should_allow_customized_error_key' do
350
+ I18n.backend.store_translations(:en, {
351
+ :activemodel => {:errors => {:messages => {:bad_transition => 'cannot %{event}'}}}
352
+ })
353
+
354
+ machine = StateMachines::Machine.new(@model, :action => :save, :messages => {:invalid_transition => :bad_transition})
355
+ machine.state :parked, :idling
356
+
357
+ record = @model.new
358
+ record.state = 'idling'
359
+
360
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
361
+ assert_equal ['State cannot ignite'], record.errors.full_messages
362
+ end
363
+
364
+ it 'should_allow_customized_error_string' do
365
+ machine = StateMachines::Machine.new(@model, :action => :save, :messages => {:invalid_transition => 'cannot %{event}'})
366
+ machine.state :parked, :idling
367
+
368
+ record = @model.new(:state => 'idling')
369
+
370
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
371
+ assert_equal ['State cannot ignite'], record.errors.full_messages
372
+ end
373
+
374
+ it 'should_allow_customized_state_key_scoped_to_class_and_machine' do
375
+ I18n.backend.store_translations(:en, {
376
+ :activemodel => {:state_machines => {:'bar/foo' => {:state => {:states => {:parked => 'shutdown'}}}}}
377
+ })
378
+
379
+ machine = StateMachines::Machine.new(@model)
380
+ machine.state :parked
381
+
382
+ assert_equal 'shutdown', machine.state(:parked).human_name
383
+ end
384
+
385
+ it 'should_allow_customized_state_key_scoped_to_class' do
386
+ I18n.backend.store_translations(:en, {
387
+ :activemodel => {:state_machines => {:'bar/foo' => {:states => {:parked => 'shutdown'}}}}
388
+ })
389
+
390
+ machine = StateMachines::Machine.new(@model)
391
+ machine.state :parked
392
+
393
+ assert_equal 'shutdown', machine.state(:parked).human_name
394
+ end
395
+
396
+ it 'should_allow_customized_state_key_scoped_to_machine' do
397
+ I18n.backend.store_translations(:en, {
398
+ :activemodel => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}}
399
+ })
400
+
401
+ machine = StateMachines::Machine.new(@model)
402
+ machine.state :parked
403
+
404
+ assert_equal 'shutdown', machine.state(:parked).human_name
405
+ end
406
+
407
+ it 'should_allow_customized_state_key_unscoped' do
408
+ I18n.backend.store_translations(:en, {
409
+ :activemodel => {:state_machines => {:states => {:parked => 'shutdown'}}}
410
+ })
411
+
412
+ machine = StateMachines::Machine.new(@model)
413
+ machine.state :parked
414
+
415
+ assert_equal 'shutdown', machine.state(:parked).human_name
416
+ end
417
+
418
+ it 'should_support_nil_state_key' do
419
+ I18n.backend.store_translations(:en, {
420
+ :activemodel => {:state_machines => {:states => {:nil => 'empty'}}}
421
+ })
422
+
423
+ machine = StateMachines::Machine.new(@model)
424
+
425
+ assert_equal 'empty', machine.state(nil).human_name
426
+ end
427
+
428
+ it 'should_allow_customized_event_key_scoped_to_class_and_machine' do
429
+ I18n.backend.store_translations(:en, {
430
+ :activemodel => {:state_machines => {:'bar/foo' => {:state => {:events => {:park => 'stop'}}}}}
431
+ })
432
+
433
+ machine = StateMachines::Machine.new(@model)
434
+ machine.event :park
435
+
436
+ assert_equal 'stop', machine.event(:park).human_name
437
+ end
438
+
439
+ it 'should_allow_customized_event_key_scoped_to_class' do
440
+ I18n.backend.store_translations(:en, {
441
+ :activemodel => {:state_machines => {:'bar/foo' => {:events => {:park => 'stop'}}}}
442
+ })
443
+
444
+ machine = StateMachines::Machine.new(@model)
445
+ machine.event :park
446
+
447
+ assert_equal 'stop', machine.event(:park).human_name
448
+ end
449
+
450
+ it 'should_allow_customized_event_key_scoped_to_machine' do
451
+ I18n.backend.store_translations(:en, {
452
+ :activemodel => {:state_machines => {:state => {:events => {:park => 'stop'}}}}
453
+ })
454
+
455
+ machine = StateMachines::Machine.new(@model)
456
+ machine.event :park
457
+
458
+ assert_equal 'stop', machine.event(:park).human_name
459
+ end
460
+
461
+ it 'should_allow_customized_event_key_unscoped' do
462
+ I18n.backend.store_translations(:en, {
463
+ :activemodel => {:state_machines => {:events => {:park => 'stop'}}}
464
+ })
465
+
466
+ machine = StateMachines::Machine.new(@model)
467
+ machine.event :park
468
+
469
+ assert_equal 'stop', machine.event(:park).human_name
470
+ end
471
+
472
+ it 'should_only_add_locale_once_in_load_path' do
473
+ assert_equal 1, I18n.load_path.select { |path| path =~ %r{active_model/locale\.rb$} }.length
474
+
475
+ # Create another ActiveModel model that will triger the i18n feature
476
+ new_model
477
+
478
+ assert_equal 1, I18n.load_path.select { |path| path =~ %r{active_model/locale\.rb$} }.length
479
+ end
480
+
481
+ context 'loading locale' do
482
+ before(:each) do
483
+ @original_load_path = I18n.load_path
484
+ I18n.backend = I18n::Backend::Simple.new
485
+ end
486
+ it 'should_add_locale_to_beginning_of_load_path' do
487
+ app_locale = File.dirname(__FILE__) + '/support/en.yml'
488
+ default_locale = File.dirname(__FILE__) + '/../lib/state_machines/integrations/active_model/locale.rb'
489
+ I18n.load_path = [app_locale]
490
+
491
+ StateMachines::Machine.new(@model)
492
+
493
+ assert_equal [default_locale, app_locale].map { |path| File.expand_path(path) }, I18n.load_path.map { |path| File.expand_path(path) }
494
+
495
+ end
496
+
497
+ it 'should_prefer_other_locales_first' do
498
+
499
+ I18n.load_path = [File.dirname(__FILE__) + '/support/en.yml']
500
+
501
+ machine = StateMachines::Machine.new(@model)
502
+ machine.state :parked, :idling
503
+ machine.event :ignite
504
+
505
+ record = @model.new(:state => 'idling')
506
+
507
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
508
+ assert_equal ['State cannot ignite'], record.errors.full_messages
509
+
510
+
511
+ end
512
+
513
+ after(:each) do
514
+ I18n.load_path = @original_load_path
515
+ end
516
+ end
517
+ end
518
+
519
+ end