aasm 4.2.0 → 4.3.0

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -1
  3. data/Gemfile +2 -2
  4. data/PLANNED_CHANGES.md +24 -4
  5. data/README.md +75 -5
  6. data/lib/aasm/aasm.rb +50 -36
  7. data/lib/aasm/base.rb +36 -18
  8. data/lib/aasm/core/event.rb +6 -5
  9. data/lib/aasm/core/state.rb +3 -2
  10. data/lib/aasm/core/transition.rb +5 -4
  11. data/lib/aasm/errors.rb +7 -4
  12. data/lib/aasm/instance_base.rb +14 -13
  13. data/lib/aasm/localizer.rb +1 -1
  14. data/lib/aasm/persistence/active_record_persistence.rb +41 -66
  15. data/lib/aasm/persistence/base.rb +7 -7
  16. data/lib/aasm/persistence/mongo_mapper_persistence.rb +34 -51
  17. data/lib/aasm/persistence/mongoid_persistence.rb +15 -36
  18. data/lib/aasm/persistence/plain_persistence.rb +8 -7
  19. data/lib/aasm/persistence/sequel_persistence.rb +12 -10
  20. data/lib/aasm/state_machine.rb +11 -6
  21. data/lib/aasm/version.rb +1 -1
  22. data/spec/database.rb +27 -1
  23. data/spec/models/active_record/basic_active_record_two_state_machines_example.rb +25 -0
  24. data/spec/models/active_record/complex_active_record_example.rb +33 -0
  25. data/spec/models/active_record/derivate_new_dsl.rb +4 -0
  26. data/spec/models/active_record/false_state.rb +18 -0
  27. data/spec/models/active_record/gate.rb +20 -0
  28. data/spec/models/active_record/no_direct_assignment.rb +11 -0
  29. data/spec/models/active_record/no_scope.rb +11 -0
  30. data/spec/models/active_record/provided_and_persisted_state.rb +3 -3
  31. data/spec/models/active_record/simple_new_dsl.rb +9 -0
  32. data/spec/models/active_record/thief.rb +15 -0
  33. data/spec/models/active_record/with_enum.rb +20 -0
  34. data/spec/models/active_record/with_false_enum.rb +16 -0
  35. data/spec/models/active_record/with_true_enum.rb +20 -0
  36. data/spec/models/basic_two_state_machines_example.rb +25 -0
  37. data/spec/models/callbacks/basic_multiple.rb +75 -0
  38. data/spec/models/callbacks/guard_within_block_multiple.rb +66 -0
  39. data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +65 -0
  40. data/spec/models/callbacks/private_method_multiple.rb +44 -0
  41. data/spec/models/callbacks/with_args_multiple.rb +61 -0
  42. data/spec/models/callbacks/{with_state_args.rb → with_state_arg.rb} +0 -0
  43. data/spec/models/callbacks/with_state_arg_multiple.rb +26 -0
  44. data/spec/models/complex_example.rb +134 -0
  45. data/spec/models/conversation.rb +47 -1
  46. data/spec/models/foo.rb +57 -0
  47. data/spec/models/foo_callback_multiple.rb +45 -0
  48. data/spec/models/guardian_multiple.rb +48 -0
  49. data/spec/models/initial_state_proc.rb +16 -0
  50. data/spec/models/invalid_persistor.rb +15 -0
  51. data/spec/models/mongo_mapper/complex_mongo_mapper_example.rb +37 -0
  52. data/spec/models/mongo_mapper/no_scope_mongo_mapper.rb +11 -0
  53. data/spec/models/mongo_mapper/simple_mongo_mapper.rb +12 -0
  54. data/spec/models/mongo_mapper/simple_new_dsl_mongo_mapper.rb +13 -0
  55. data/spec/models/mongoid/complex_mongoid_example.rb +37 -0
  56. data/spec/models/mongoid/no_scope_mongoid.rb +11 -0
  57. data/spec/models/mongoid/simple_mongoid.rb +12 -0
  58. data/spec/models/mongoid/simple_new_dsl_mongoid.rb +13 -0
  59. data/spec/models/no_initial_state.rb +13 -0
  60. data/spec/models/parametrised_event.rb +1 -1
  61. data/spec/models/parametrised_event_multiple.rb +29 -0
  62. data/spec/models/provided_state.rb +3 -3
  63. data/spec/models/sequel/complex_sequel_example.rb +45 -0
  64. data/spec/models/sequel/sequel_multiple.rb +25 -0
  65. data/spec/models/sequel/sequel_simple.rb +25 -0
  66. data/spec/models/simple_multiple_example.rb +30 -0
  67. data/spec/models/sub_class.rb +4 -0
  68. data/spec/models/sub_class_with_more_states.rb +11 -0
  69. data/spec/models/super_class.rb +28 -0
  70. data/spec/models/transactor.rb +27 -0
  71. data/spec/models/valid_state_name.rb +12 -0
  72. data/spec/models/validator.rb +39 -0
  73. data/spec/unit/basic_two_state_machines_example_spec.rb +10 -0
  74. data/spec/unit/callback_multiple_spec.rb +295 -0
  75. data/spec/unit/callbacks_spec.rb +1 -1
  76. data/spec/unit/complex_multiple_example_spec.rb +99 -0
  77. data/spec/unit/edge_cases_spec.rb +16 -0
  78. data/spec/unit/event_multiple_spec.rb +73 -0
  79. data/spec/unit/event_spec.rb +11 -6
  80. data/spec/unit/guard_multiple_spec.rb +60 -0
  81. data/spec/unit/initial_state_multiple_spec.rb +15 -0
  82. data/spec/unit/inspection_multiple_spec.rb +201 -0
  83. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +560 -0
  84. data/spec/unit/persistence/active_record_persistence_spec.rb +17 -12
  85. data/spec/unit/persistence/mongo_mapper_persistence_multiple_spec.rb +146 -0
  86. data/spec/unit/persistence/{mongo_mapper_persistance_spec.rb → mongo_mapper_persistence_spec.rb} +7 -49
  87. data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +127 -0
  88. data/spec/unit/persistence/mongoid_persistence_spec.rb +79 -0
  89. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +153 -0
  90. data/spec/unit/persistence/sequel_persistence_spec.rb +7 -24
  91. data/spec/unit/reloading_spec.rb +1 -1
  92. data/spec/unit/simple_multiple_example_spec.rb +63 -0
  93. data/spec/unit/state_spec.rb +3 -1
  94. data/spec/unit/subclassing_multiple_spec.rb +39 -0
  95. data/spec/unit/transition_spec.rb +31 -22
  96. metadata +73 -9
  97. data/spec/unit/persistence/mongoid_persistance_spec.rb +0 -146
@@ -1,8 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe 'adding an event' do
4
+ let(:state_machine) { AASM::StateMachine.new(:name) }
4
5
  let(:event) do
5
- AASM::Core::Event.new(:close_order, {:success => :success_callback}) do
6
+ AASM::Core::Event.new(:close_order, state_machine, {:success => :success_callback}) do
6
7
  before :before_callback
7
8
  after :after_callback
8
9
  transitions :to => :closed, :from => [:open, :received]
@@ -35,8 +36,9 @@ describe 'adding an event' do
35
36
  end
36
37
 
37
38
  describe 'transition inspection' do
39
+ let(:state_machine) { AASM::StateMachine.new(:name) }
38
40
  let(:event) do
39
- AASM::Core::Event.new(:run) do
41
+ AASM::Core::Event.new(:run, state_machine) do
40
42
  transitions :to => :running, :from => :sleeping
41
43
  end
42
44
  end
@@ -59,8 +61,9 @@ describe 'transition inspection' do
59
61
  end
60
62
 
61
63
  describe 'transition inspection without from' do
64
+ let(:state_machine) { AASM::StateMachine.new(:name) }
62
65
  let(:event) do
63
- AASM::Core::Event.new(:run) do
66
+ AASM::Core::Event.new(:run, state_machine) do
64
67
  transitions :to => :running
65
68
  end
66
69
  end
@@ -76,15 +79,17 @@ describe 'transition inspection without from' do
76
79
  end
77
80
 
78
81
  describe 'firing an event' do
82
+ let(:state_machine) { AASM::StateMachine.new(:name) }
83
+
79
84
  it 'should return nil if the transitions are empty' do
80
85
  obj = double('object', :aasm => double('aasm', :current_state => 'open'))
81
86
 
82
- event = AASM::Core::Event.new(:event)
87
+ event = AASM::Core::Event.new(:event, state_machine)
83
88
  expect(event.fire(obj)).to be_nil
84
89
  end
85
90
 
86
91
  it 'should return the state of the first matching transition it finds' do
87
- event = AASM::Core::Event.new(:event) do
92
+ event = AASM::Core::Event.new(:event, state_machine) do
88
93
  transitions :to => :closed, :from => [:open, :received]
89
94
  end
90
95
 
@@ -94,7 +99,7 @@ describe 'firing an event' do
94
99
  end
95
100
 
96
101
  it 'should call the guard with the params passed in' do
97
- event = AASM::Core::Event.new(:event) do
102
+ event = AASM::Core::Event.new(:event, state_machine) do
98
103
  transitions :to => :closed, :from => [:open, :received], :guard => :guard_fn
99
104
  end
100
105
 
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe "per-transition guards" do
4
+ let(:guardian) { GuardianMultiple.new }
5
+
6
+ it "allows the transition if the guard succeeds" do
7
+ expect { guardian.use_one_guard_that_succeeds! }.to_not raise_error
8
+ expect(guardian).to be_beta
9
+ end
10
+
11
+ it "stops the transition if the guard fails" do
12
+ expect { guardian.use_one_guard_that_fails! }.to raise_error(AASM::InvalidTransition)
13
+ expect(guardian).to be_alpha
14
+ end
15
+
16
+ it "allows the transition if all guards succeeds" do
17
+ expect { guardian.use_guards_that_succeed! }.to_not raise_error
18
+ expect(guardian).to be_beta
19
+ end
20
+
21
+ it "stops the transition if the first guard fails" do
22
+ expect { guardian.use_guards_where_the_first_fails! }.to raise_error(AASM::InvalidTransition)
23
+ expect(guardian).to be_alpha
24
+ end
25
+
26
+ it "stops the transition if the second guard fails" do
27
+ expect { guardian.use_guards_where_the_second_fails! }.to raise_error(AASM::InvalidTransition)
28
+ expect(guardian).to be_alpha
29
+ end
30
+ end
31
+
32
+ describe "event guards" do
33
+ let(:guardian) { GuardianMultiple.new }
34
+
35
+ it "allows the transition if the event guards succeed" do
36
+ expect { guardian.use_event_guards_that_succeed! }.to_not raise_error
37
+ expect(guardian).to be_beta
38
+ end
39
+
40
+ it "allows the transition if the event and transition guards succeed" do
41
+ expect { guardian.use_event_and_transition_guards_that_succeed! }.to_not raise_error
42
+ expect(guardian).to be_beta
43
+ end
44
+
45
+ it "stops the transition if the first event guard fails" do
46
+ expect { guardian.use_event_guards_where_the_first_fails! }.to raise_error(AASM::InvalidTransition)
47
+ expect(guardian).to be_alpha
48
+ end
49
+
50
+ it "stops the transition if the second event guard fails" do
51
+ expect { guardian.use_event_guards_where_the_second_fails! }.to raise_error(AASM::InvalidTransition)
52
+ expect(guardian).to be_alpha
53
+ end
54
+
55
+ it "stops the transition if the transition guard fails" do
56
+ expect { guardian.use_event_and_transition_guards_where_third_fails! }.to raise_error(AASM::InvalidTransition)
57
+ expect(guardian).to be_alpha
58
+ end
59
+
60
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'initial states' do
4
+ it 'should use the first state defined if no initial state is given' do
5
+ expect(NoInitialStateMultiple.new.aasm(:left).current_state).to eq(:read)
6
+ end
7
+
8
+ it 'should determine initial state from the Proc results' do
9
+ balance = InitialStateProcMultiple::RICH - 1
10
+ expect(InitialStateProcMultiple.new(balance).aasm(:left).current_state).to eq(:selling_bad_mortgages)
11
+
12
+ balance = InitialStateProcMultiple::RICH + 1
13
+ expect(InitialStateProcMultiple.new(balance).aasm(:left).current_state).to eq(:retired)
14
+ end
15
+ end
@@ -0,0 +1,201 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'inspection for common cases' do
4
+ it 'should support the new DSL' do
5
+ # 1st state machine
6
+ expect(FooMultiple.aasm(:left)).to respond_to(:states)
7
+ expect(FooMultiple.aasm(:left).states.size).to eql 3
8
+ expect(FooMultiple.aasm(:left).states).to include(:open)
9
+ expect(FooMultiple.aasm(:left).states).to include(:closed)
10
+ expect(FooMultiple.aasm(:left).states).to include(:final)
11
+
12
+ expect(FooMultiple.aasm(:left)).to respond_to(:initial_state)
13
+ expect(FooMultiple.aasm(:left).initial_state).to eq(:open)
14
+
15
+ expect(FooMultiple.aasm(:left)).to respond_to(:events)
16
+ expect(FooMultiple.aasm(:left).events.size).to eql 2
17
+ expect(FooMultiple.aasm(:left).events).to include(:close)
18
+ expect(FooMultiple.aasm(:left).events).to include(:null)
19
+
20
+ # 2nd state machine
21
+ expect(FooMultiple.aasm(:right)).to respond_to(:states)
22
+ expect(FooMultiple.aasm(:right).states.size).to eql 3
23
+ expect(FooMultiple.aasm(:right).states).to include(:green)
24
+ expect(FooMultiple.aasm(:right).states).to include(:yellow)
25
+ expect(FooMultiple.aasm(:right).states).to include(:red)
26
+
27
+ expect(FooMultiple.aasm(:right)).to respond_to(:initial_state)
28
+ expect(FooMultiple.aasm(:right).initial_state).to eq(:green)
29
+
30
+ expect(FooMultiple.aasm(:right)).to respond_to(:events)
31
+ expect(FooMultiple.aasm(:right).events.size).to eql 3
32
+ expect(FooMultiple.aasm(:right).events).to include(:green)
33
+ expect(FooMultiple.aasm(:right).events).to include(:yellow)
34
+ expect(FooMultiple.aasm(:right).events).to include(:red)
35
+ end
36
+
37
+ context "instance level inspection" do
38
+ let(:foo) { FooMultiple.new }
39
+ let(:two) { FooTwoMultiple.new }
40
+
41
+ it "delivers all states" do
42
+ # 1st state machine
43
+ states = foo.aasm(:left).states
44
+ expect(states.size).to eql 3
45
+ expect(states).to include(:open)
46
+ expect(states).to include(:closed)
47
+ expect(states).to include(:final)
48
+
49
+ states = foo.aasm(:left).states(:permitted => true)
50
+ expect(states.size).to eql 1
51
+ expect(states).to include(:closed)
52
+ expect(states).not_to include(:open)
53
+ expect(states).not_to include(:final)
54
+
55
+ foo.close
56
+ expect(foo.aasm(:left).states(:permitted => true)).to be_empty
57
+
58
+ # 2nd state machine
59
+ states = foo.aasm(:right).states
60
+ expect(states.size).to eql 3
61
+ expect(states).to include(:green)
62
+ expect(states).to include(:yellow)
63
+ expect(states).to include(:red)
64
+
65
+ states = foo.aasm(:right).states(:permitted => true)
66
+ expect(states.size).to eql 1
67
+ expect(states).to include(:yellow)
68
+ expect(states).not_to include(:green)
69
+ expect(states).not_to include(:red)
70
+
71
+ foo.yellow
72
+ states = foo.aasm(:right).states(:permitted => true)
73
+ expect(states.size).to eql 2
74
+ expect(states).to include(:red)
75
+ expect(states).to include(:green)
76
+ expect(states).not_to include(:yellow)
77
+ end
78
+
79
+ it "delivers all states for subclasses" do
80
+ # 1st state machine
81
+ states = two.aasm(:left).states
82
+ expect(states.size).to eql 4
83
+ expect(states).to include(:open)
84
+ expect(states).to include(:closed)
85
+ expect(states).to include(:final)
86
+ expect(states).to include(:foo)
87
+
88
+ states = two.aasm(:left).states(:permitted => true)
89
+ expect(states.size).to eql 1
90
+ expect(states).to include(:closed)
91
+ expect(states).not_to include(:open)
92
+
93
+ two.close
94
+ expect(two.aasm(:left).states(:permitted => true)).to be_empty
95
+
96
+ # 2nd state machine
97
+ states = two.aasm(:right).states
98
+ expect(states.size).to eql 4
99
+ expect(states).to include(:green)
100
+ expect(states).to include(:yellow)
101
+ expect(states).to include(:red)
102
+ expect(states).to include(:bar)
103
+
104
+ states = two.aasm(:right).states(:permitted => true)
105
+ expect(states.size).to eql 1
106
+ expect(states).to include(:yellow)
107
+ expect(states).not_to include(:red)
108
+ expect(states).not_to include(:green)
109
+ expect(states).not_to include(:bar)
110
+
111
+ two.yellow
112
+ states = two.aasm(:right).states(:permitted => true)
113
+ expect(states.size).to eql 2
114
+ expect(states).to include(:green)
115
+ expect(states).to include(:red)
116
+ expect(states).not_to include(:yellow)
117
+ expect(states).not_to include(:bar)
118
+ end
119
+
120
+ it "delivers all events" do
121
+ # 1st state machine
122
+ events = foo.aasm(:left).events
123
+ expect(events.size).to eql 2
124
+ expect(events).to include(:close)
125
+ expect(events).to include(:null)
126
+
127
+ foo.close
128
+ expect(foo.aasm(:left).events).to be_empty
129
+
130
+ # 2nd state machine
131
+ events = foo.aasm(:right).events
132
+ expect(events.size).to eql 1
133
+ expect(events).to include(:yellow)
134
+ expect(events).not_to include(:green)
135
+ expect(events).not_to include(:red)
136
+
137
+ foo.yellow
138
+ events = foo.aasm(:right).events
139
+ expect(events.size).to eql 2
140
+ expect(events).to include(:green)
141
+ expect(events).to include(:red)
142
+ expect(events).not_to include(:yellow)
143
+ end
144
+ end
145
+
146
+ it 'should list states in the order they have been defined' do
147
+ expect(ConversationMultiple.aasm(:left).states).to eq([
148
+ :needs_attention, :read, :closed, :awaiting_response, :junk
149
+ ])
150
+ end
151
+ end
152
+
153
+ describe "special cases" do
154
+ it "should support valid as state name" do
155
+ expect(ValidStateNameMultiple.aasm(:left).states).to include(:invalid)
156
+ expect(ValidStateNameMultiple.aasm(:left).states).to include(:valid)
157
+
158
+ argument = ValidStateNameMultiple.new
159
+ expect(argument.invalid?).to be_truthy
160
+ expect(argument.aasm(:left).current_state).to eq(:invalid)
161
+
162
+ argument.valid!
163
+ expect(argument.valid?).to be_truthy
164
+ expect(argument.aasm(:left).current_state).to eq(:valid)
165
+ end
166
+ end
167
+
168
+ describe 'aasm.states_for_select' do
169
+ it "should return a select friendly array of states" do
170
+ expect(FooMultiple.aasm(:left)).to respond_to(:states_for_select)
171
+ expect(FooMultiple.aasm(:left).states_for_select).to eq(
172
+ [['Open', 'open'], ['Closed', 'closed'], ['Final', 'final']]
173
+ )
174
+ end
175
+ end
176
+
177
+ describe 'aasm.from_states_for_state' do
178
+ it "should return all from states for a state" do
179
+ expect(ComplexExampleMultiple.aasm(:left)).to respond_to(:from_states_for_state)
180
+ froms = ComplexExampleMultiple.aasm(:left).from_states_for_state(:active)
181
+ [:pending, :passive, :suspended].each {|from| expect(froms).to include(from)}
182
+ end
183
+
184
+ it "should return from states for a state for a particular transition only" do
185
+ froms = ComplexExampleMultiple.aasm(:left).from_states_for_state(:active, :transition => :left_unsuspend)
186
+ [:suspended].each {|from| expect(froms).to include(from)}
187
+ end
188
+ end
189
+
190
+ describe 'permitted events' do
191
+ let(:foo) {FooMultiple.new}
192
+
193
+ it 'work' do
194
+ expect(foo.aasm(:left).events(:permitted => true)).to include(:close)
195
+ expect(foo.aasm(:left).events(:permitted => true)).not_to include(:null)
196
+
197
+ expect(foo.aasm(:right).events(:permitted => true)).to include(:yellow)
198
+ expect(foo.aasm(:right).events(:permitted => true)).not_to include(:green)
199
+ expect(foo.aasm(:right).events(:permitted => true)).not_to include(:red)
200
+ end
201
+ end
@@ -0,0 +1,560 @@
1
+ require 'active_record'
2
+ require 'spec_helper'
3
+ Dir[File.dirname(__FILE__) + "/../../models/active_record/*.rb"].sort.each do |f|
4
+ require File.expand_path(f)
5
+ end
6
+
7
+ load_schema
8
+
9
+ # if you want to see the statements while running the spec enable the following line
10
+ # require 'logger'
11
+ # ActiveRecord::Base.logger = Logger.new(STDERR)
12
+
13
+ describe "instance methods" do
14
+ let(:gate) {MultipleGate.new}
15
+
16
+ it "should respond to aasm persistence methods" do
17
+ expect(gate).to respond_to(:aasm_read_state)
18
+ expect(gate).to respond_to(:aasm_write_state)
19
+ expect(gate).to respond_to(:aasm_write_state_without_persistence)
20
+ end
21
+
22
+ describe "aasm_column_looks_like_enum" do
23
+ subject { lambda{ gate.send(:aasm_column_looks_like_enum, :left) } }
24
+
25
+ let(:column_name) { "value" }
26
+ let(:columns_hash) { Hash[column_name, column] }
27
+
28
+ before :each do
29
+ allow(gate.class.aasm(:left)).to receive(:attribute_name).and_return(column_name.to_sym)
30
+ allow(gate.class).to receive(:columns_hash).and_return(columns_hash)
31
+ end
32
+
33
+ context "when AASM column has integer type" do
34
+ let(:column) { double(Object, type: :integer) }
35
+
36
+ it "returns true" do
37
+ expect(subject.call).to be_truthy
38
+ end
39
+ end
40
+
41
+ context "when AASM column has string type" do
42
+ let(:column) { double(Object, type: :string) }
43
+
44
+ it "returns false" do
45
+ expect(subject.call).to be_falsey
46
+ end
47
+ end
48
+ end
49
+
50
+ describe "aasm_guess_enum_method" do
51
+ subject { lambda{ gate.send(:aasm_guess_enum_method, :left) } }
52
+
53
+ before :each do
54
+ allow(gate.class.aasm(:left)).to receive(:attribute_name).and_return(:value)
55
+ end
56
+
57
+ it "pluralizes AASM column name" do
58
+ expect(subject.call).to eq :values
59
+ end
60
+ end
61
+
62
+ describe "aasm_enum" do
63
+ context "when AASM enum setting contains an explicit enum method name" do
64
+ let(:with_enum) { MultipleWithEnum.new }
65
+
66
+ it "returns whatever value was set in AASM config" do
67
+ expect(with_enum.send(:aasm_enum, :left)).to eq :test
68
+ end
69
+ end
70
+
71
+ context "when AASM enum setting is simply set to true" do
72
+ let(:with_true_enum) { MultipleWithTrueEnum.new }
73
+ before :each do
74
+ allow(MultipleWithTrueEnum.aasm(:left)).to receive(:attribute_name).and_return(:value)
75
+ end
76
+
77
+ it "infers enum method name from pluralized column name" do
78
+ expect(with_true_enum.send(:aasm_enum, :left)).to eq :values
79
+ end
80
+ end
81
+
82
+ context "when AASM enum setting is explicitly disabled" do
83
+ let(:with_false_enum) { MultipleWithFalseEnum.new }
84
+
85
+ it "returns nil" do
86
+ expect(with_false_enum.send(:aasm_enum, :left)).to be_nil
87
+ end
88
+ end
89
+
90
+ context "when AASM enum setting is not enabled" do
91
+ before :each do
92
+ allow(MultipleGate.aasm(:left)).to receive(:attribute_name).and_return(:value)
93
+ end
94
+
95
+ context "when AASM column looks like enum" do
96
+ before :each do
97
+ allow(gate).to receive(:aasm_column_looks_like_enum).with(:left).and_return(true)
98
+ end
99
+
100
+ it "infers enum method name from pluralized column name" do
101
+ expect(gate.send(:aasm_enum, :left)).to eq :values
102
+ end
103
+ end
104
+
105
+ context "when AASM column doesn't look like enum'" do
106
+ before :each do
107
+ allow(gate).to receive(:aasm_column_looks_like_enum)
108
+ .and_return(false)
109
+ end
110
+
111
+ it "returns nil, as we're not using enum" do
112
+ expect(gate.send(:aasm_enum, :left)).to be_nil
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ context "when AASM is configured to use enum" do
119
+ let(:state_sym) { :running }
120
+ let(:state_code) { 2 }
121
+ let(:enum_name) { :states }
122
+ let(:enum) { Hash[state_sym, state_code] }
123
+
124
+ before :each do
125
+ allow(gate).to receive(:aasm_enum).and_return(enum_name)
126
+ allow(gate).to receive(:aasm_write_attribute)
127
+ allow(gate).to receive(:write_attribute)
128
+
129
+ allow(MultipleGate).to receive(enum_name).and_return(enum)
130
+ end
131
+
132
+ describe "aasm_write_state" do
133
+ context "when AASM is configured to skip validations on save" do
134
+ before :each do
135
+ allow(gate).to receive(:aasm_skipping_validations).and_return(true)
136
+ end
137
+
138
+ it "passes state code instead of state symbol to update_all" do
139
+ # stub_chain does not allow us to give expectations on call
140
+ # parameters in the middle of the chain, so we need to use
141
+ # intermediate object instead.
142
+ obj = double(Object, update_all: 1)
143
+ allow(MultipleGate).to receive(:where).and_return(obj)
144
+
145
+ gate.aasm_write_state state_sym, :left
146
+
147
+ expect(obj).to have_received(:update_all)
148
+ .with(Hash[gate.class.aasm(:left).attribute_name, state_code])
149
+ end
150
+ end
151
+
152
+ context "when AASM is not skipping validations" do
153
+ it "delegates state update to the helper method" do
154
+ # Let's pretend that validation is passed
155
+ allow(gate).to receive(:save).and_return(true)
156
+
157
+ gate.aasm_write_state state_sym, :left
158
+
159
+ expect(gate).to have_received(:aasm_write_attribute).with(state_sym, :left)
160
+ expect(gate).to_not have_received :write_attribute
161
+ end
162
+ end
163
+ end
164
+
165
+ describe "aasm_write_state_without_persistence" do
166
+ it "delegates state update to the helper method" do
167
+ gate.aasm_write_state_without_persistence state_sym, :left
168
+
169
+ expect(gate).to have_received(:aasm_write_attribute).with(state_sym, :left)
170
+ expect(gate).to_not have_received :write_attribute
171
+ end
172
+ end
173
+
174
+ describe "aasm_raw_attribute_value" do
175
+ it "converts state symbol to state code" do
176
+ expect(gate.send(:aasm_raw_attribute_value, state_sym))
177
+ .to eq state_code
178
+ end
179
+ end
180
+ end
181
+
182
+ context "when AASM is configured to use string field" do
183
+ let(:state_sym) { :running }
184
+
185
+ before :each do
186
+ allow(gate).to receive(:aasm_enum).and_return(nil)
187
+ end
188
+
189
+ describe "aasm_raw_attribute_value" do
190
+ it "converts state symbol to string" do
191
+ expect(gate.send(:aasm_raw_attribute_value, state_sym))
192
+ .to eq state_sym.to_s
193
+ end
194
+ end
195
+ end
196
+
197
+ describe "aasm_write_attribute helper method" do
198
+ let(:sym) { :sym }
199
+ let(:value) { 42 }
200
+
201
+ before :each do
202
+ allow(gate).to receive(:write_attribute)
203
+ allow(gate).to receive(:aasm_raw_attribute_value).and_return(value)
204
+
205
+ gate.send(:aasm_write_attribute, sym, :left)
206
+ end
207
+
208
+ it "generates attribute value using a helper method" do
209
+ expect(gate).to have_received(:aasm_raw_attribute_value).with(sym, :left)
210
+ end
211
+
212
+ it "writes attribute to the model" do
213
+ expect(gate).to have_received(:write_attribute).with(:aasm_state, value)
214
+ end
215
+ end
216
+
217
+ it "should return the initial state when new and the aasm field is nil" do
218
+ expect(gate.aasm(:left).current_state).to eq(:opened)
219
+ end
220
+
221
+ it "should return the aasm column when new and the aasm field is not nil" do
222
+ gate.aasm_state = "closed"
223
+ expect(gate.aasm(:left).current_state).to eq(:closed)
224
+ end
225
+
226
+ it "should return the aasm column when not new and the aasm.attribute_name is not nil" do
227
+ allow(gate).to receive(:new_record?).and_return(false)
228
+ gate.aasm_state = "state"
229
+ expect(gate.aasm(:left).current_state).to eq(:state)
230
+ end
231
+
232
+ it "should allow a nil state" do
233
+ allow(gate).to receive(:new_record?).and_return(false)
234
+ gate.aasm_state = nil
235
+ expect(gate.aasm(:left).current_state).to be_nil
236
+ end
237
+
238
+ context 'on initialization' do
239
+ it "should initialize the aasm state" do
240
+ expect(MultipleGate.new.aasm_state).to eql 'opened'
241
+ expect(MultipleGate.new.aasm(:left).current_state).to eql :opened
242
+ end
243
+
244
+ it "should not initialize the aasm state if it has not been loaded" do
245
+ # we have to create a gate in the database, for which we only want to
246
+ # load the id, and not the state
247
+ gate = MultipleGate.create!
248
+
249
+ # then we just load the gate ids
250
+ MultipleGate.select(:id).where(id: gate.id).first
251
+ end
252
+ end
253
+
254
+ end
255
+
256
+ if ActiveRecord::VERSION::MAJOR < 4 && ActiveRecord::VERSION::MINOR < 2 # won't work with Rails >= 4.2
257
+ describe "direct state column access" do
258
+ it "accepts false states" do
259
+ f = MultipleFalseState.create!
260
+ expect(f.aasm_state).to eql false
261
+ expect {
262
+ f.aasm(:left).events.map(&:name)
263
+ }.to_not raise_error
264
+ end
265
+ end
266
+ end
267
+
268
+ describe 'subclasses' do
269
+ it "should have the same states as its parent class" do
270
+ expect(MultipleDerivateNewDsl.aasm(:left).states).to eq(MultipleSimpleNewDsl.aasm(:left).states)
271
+ end
272
+
273
+ it "should have the same events as its parent class" do
274
+ expect(MultipleDerivateNewDsl.aasm(:left).events).to eq(MultipleSimpleNewDsl.aasm(:left).events)
275
+ end
276
+
277
+ it "should have the same column as its parent even for the new dsl" do
278
+ expect(MultipleSimpleNewDsl.aasm(:left).attribute_name).to eq(:status)
279
+ expect(MultipleDerivateNewDsl.aasm(:left).attribute_name).to eq(:status)
280
+ end
281
+ end
282
+
283
+ describe "named scopes with the new DSL" do
284
+ context "Does not already respond_to? the scope name" do
285
+ it "should add a scope" do
286
+ expect(MultipleSimpleNewDsl).to respond_to(:unknown_scope)
287
+ expect(MultipleSimpleNewDsl.unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
288
+ end
289
+ end
290
+
291
+ context "Already respond_to? the scope name" do
292
+ it "should not add a scope" do
293
+ expect(MultipleSimpleNewDsl).to respond_to(:new)
294
+ expect(MultipleSimpleNewDsl.new.class).to eq(MultipleSimpleNewDsl)
295
+ end
296
+ end
297
+
298
+ it "does not create scopes if requested" do
299
+ expect(MultipleNoScope).not_to respond_to(:pending)
300
+ end
301
+ end # scopes
302
+
303
+ describe "direct assignment" do
304
+ it "is allowed by default" do
305
+ obj = MultipleNoScope.create
306
+ expect(obj.aasm_state.to_sym).to eql :pending
307
+
308
+ obj.aasm_state = :running
309
+ expect(obj.aasm_state.to_sym).to eql :running
310
+ end
311
+
312
+ it "is forbidden if configured" do
313
+ obj = MultipleNoDirectAssignment.create
314
+ expect(obj.aasm_state.to_sym).to eql :pending
315
+
316
+ expect {obj.aasm_state = :running}.to raise_error(AASM::NoDirectAssignmentError)
317
+ expect(obj.aasm_state.to_sym).to eql :pending
318
+ end
319
+
320
+ it 'can be turned off and on again' do
321
+ obj = MultipleNoDirectAssignment.create
322
+ expect(obj.aasm_state.to_sym).to eql :pending
323
+
324
+ expect {obj.aasm_state = :running}.to raise_error(AASM::NoDirectAssignmentError)
325
+ expect(obj.aasm_state.to_sym).to eql :pending
326
+
327
+ # allow it temporarily
328
+ MultipleNoDirectAssignment.aasm(:left).state_machine.config.no_direct_assignment = false
329
+ obj.aasm_state = :pending
330
+ expect(obj.aasm_state.to_sym).to eql :pending
331
+
332
+ # and forbid it again
333
+ MultipleNoDirectAssignment.aasm(:left).state_machine.config.no_direct_assignment = true
334
+ expect {obj.aasm_state = :running}.to raise_error(AASM::NoDirectAssignmentError)
335
+ expect(obj.aasm_state.to_sym).to eql :pending
336
+ end
337
+ end # direct assignment
338
+
339
+ describe 'initial states' do
340
+ it 'should support conditions' do
341
+ expect(MultipleThief.new(:skilled => true).aasm(:left).current_state).to eq(:rich)
342
+ expect(MultipleThief.new(:skilled => false).aasm(:left).current_state).to eq(:jailed)
343
+ end
344
+ end
345
+
346
+ describe 'transitions with persistence' do
347
+
348
+ it "should work for valid models" do
349
+ valid_object = MultipleValidator.create(:name => 'name')
350
+ expect(valid_object).to be_sleeping
351
+ valid_object.status = :running
352
+ expect(valid_object).to be_running
353
+ end
354
+
355
+ it 'should not store states for invalid models' do
356
+ validator = MultipleValidator.create(:name => 'name')
357
+ expect(validator).to be_valid
358
+ expect(validator).to be_sleeping
359
+
360
+ validator.name = nil
361
+ expect(validator).not_to be_valid
362
+ expect(validator.run!).to be_falsey
363
+ expect(validator).to be_sleeping
364
+
365
+ validator.reload
366
+ expect(validator).not_to be_running
367
+ expect(validator).to be_sleeping
368
+
369
+ validator.name = 'another name'
370
+ expect(validator).to be_valid
371
+ expect(validator.run!).to be_truthy
372
+ expect(validator).to be_running
373
+
374
+ validator.reload
375
+ expect(validator).to be_running
376
+ expect(validator).not_to be_sleeping
377
+ end
378
+
379
+ it 'should store states for invalid models if configured' do
380
+ persistor = MultipleInvalidPersistor.create(:name => 'name')
381
+ expect(persistor).to be_valid
382
+ expect(persistor).to be_sleeping
383
+
384
+ persistor.name = nil
385
+ expect(persistor).not_to be_valid
386
+ expect(persistor.run!).to be_truthy
387
+ expect(persistor).to be_running
388
+
389
+ persistor = MultipleInvalidPersistor.find(persistor.id)
390
+ persistor.valid?
391
+ expect(persistor).to be_valid
392
+ expect(persistor).to be_running
393
+ expect(persistor).not_to be_sleeping
394
+
395
+ persistor.reload
396
+ expect(persistor).to be_running
397
+ expect(persistor).not_to be_sleeping
398
+ end
399
+
400
+ describe 'transactions' do
401
+ let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') }
402
+ let(:transactor) { MultipleTransactor.create!(:name => 'transactor', :worker => worker) }
403
+
404
+ it 'should rollback all changes' do
405
+ expect(transactor).to be_sleeping
406
+ expect(worker.status).to eq('sleeping')
407
+
408
+ expect {transactor.run!}.to raise_error(StandardError, 'failed on purpose')
409
+ expect(transactor).to be_running
410
+ expect(worker.reload.status).to eq('sleeping')
411
+ end
412
+
413
+ context "nested transactions" do
414
+ it "should rollback all changes in nested transaction" do
415
+ expect(transactor).to be_sleeping
416
+ expect(worker.status).to eq('sleeping')
417
+
418
+ Worker.transaction do
419
+ expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
420
+ end
421
+
422
+ expect(transactor).to be_running
423
+ expect(worker.reload.status).to eq('sleeping')
424
+ end
425
+
426
+ it "should only rollback changes in the main transaction not the nested one" do
427
+ # change configuration to not require new transaction
428
+ AASM::StateMachine[MultipleTransactor][:left].config.requires_new_transaction = false
429
+
430
+ expect(transactor).to be_sleeping
431
+ expect(worker.status).to eq('sleeping')
432
+
433
+ Worker.transaction do
434
+ expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
435
+ end
436
+
437
+ expect(transactor).to be_running
438
+ expect(worker.reload.status).to eq('running')
439
+ end
440
+ end
441
+
442
+ describe "after_commit callback" do
443
+ it "should fire :after_commit if transaction was successful" do
444
+ validator = MultipleValidator.create(:name => 'name')
445
+ expect(validator).to be_sleeping
446
+
447
+ validator.run!
448
+ expect(validator).to be_running
449
+ expect(validator.name).to eq("name changed")
450
+
451
+ validator.sleep!("sleeper")
452
+ expect(validator).to be_sleeping
453
+ expect(validator.name).to eq("sleeper")
454
+ end
455
+
456
+ it "should not fire :after_commit if transaction failed" do
457
+ validator = MultipleValidator.create(:name => 'name')
458
+ expect { validator.fail! }.to raise_error(StandardError, 'failed on purpose')
459
+ expect(validator.name).to eq("name")
460
+ end
461
+
462
+ it "should not fire if not saving" do
463
+ validator = MultipleValidator.create(:name => 'name')
464
+ expect(validator).to be_sleeping
465
+ validator.run
466
+ expect(validator).to be_running
467
+ expect(validator.name).to eq("name")
468
+ end
469
+
470
+ end
471
+
472
+ context "when not persisting" do
473
+ it 'should not rollback all changes' do
474
+ expect(transactor).to be_sleeping
475
+ expect(worker.status).to eq('sleeping')
476
+
477
+ # Notice here we're calling "run" and not "run!" with a bang.
478
+ expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
479
+ expect(transactor).to be_running
480
+ expect(worker.reload.status).to eq('running')
481
+ end
482
+
483
+ it 'should not create a database transaction' do
484
+ expect(transactor.class).not_to receive(:transaction)
485
+ expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
486
+ end
487
+ end
488
+ end
489
+ end
490
+
491
+ describe "invalid states with persistence" do
492
+ it "should not store states" do
493
+ validator = MultipleValidator.create(:name => 'name')
494
+ validator.status = 'invalid_state'
495
+ expect(validator.save).to be_falsey
496
+ expect {validator.save!}.to raise_error(ActiveRecord::RecordInvalid)
497
+
498
+ validator.reload
499
+ expect(validator).to be_sleeping
500
+ end
501
+
502
+ it "should store invalid states if configured" do
503
+ persistor = MultipleInvalidPersistor.create(:name => 'name')
504
+ persistor.status = 'invalid_state'
505
+ expect(persistor.save).to be_truthy
506
+
507
+ persistor.reload
508
+ expect(persistor.status).to eq('invalid_state')
509
+ end
510
+ end
511
+
512
+ describe "complex example" do
513
+ it "works" do
514
+ record = ComplexActiveRecordExample.new
515
+ expect_aasm_states record, :one, :alpha
516
+
517
+ record.save!
518
+ expect_aasm_states record, :one, :alpha
519
+ record.reload
520
+ expect_aasm_states record, :one, :alpha
521
+
522
+ record.increment!
523
+ expect_aasm_states record, :two, :alpha
524
+ record.reload
525
+ expect_aasm_states record, :two, :alpha
526
+
527
+ record.level_up!
528
+ expect_aasm_states record, :two, :beta
529
+ record.reload
530
+ expect_aasm_states record, :two, :beta
531
+
532
+ record.increment!
533
+ expect { record.increment! }.to raise_error(AASM::InvalidTransition)
534
+ expect_aasm_states record, :three, :beta
535
+ record.reload
536
+ expect_aasm_states record, :three, :beta
537
+
538
+ record.level_up!
539
+ expect_aasm_states record, :three, :gamma
540
+ record.reload
541
+ expect_aasm_states record, :three, :gamma
542
+
543
+ record.level_down # without saving
544
+ expect_aasm_states record, :three, :beta
545
+ record.reload
546
+ expect_aasm_states record, :three, :gamma
547
+
548
+ record.level_down # without saving
549
+ expect_aasm_states record, :three, :beta
550
+ record.reset!
551
+ expect_aasm_states record, :one, :beta
552
+ end
553
+
554
+ def expect_aasm_states(record, left_state, right_state)
555
+ expect(record.aasm(:left).current_state).to eql left_state.to_sym
556
+ expect(record.left).to eql left_state.to_s
557
+ expect(record.aasm(:right).current_state).to eql right_state.to_sym
558
+ expect(record.right).to eql right_state.to_s
559
+ end
560
+ end