aasm 4.12.0 → 4.12.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -1
  3. data/Appraisals +2 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Gemfile +1 -1
  6. data/README.md +102 -8
  7. data/gemfiles/rails_4.2.gemfile +1 -0
  8. data/gemfiles/rails_5.0.gemfile +1 -0
  9. data/lib/aasm/base.rb +8 -0
  10. data/lib/aasm/core/event.rb +1 -1
  11. data/lib/aasm/core/transition.rb +1 -1
  12. data/lib/aasm/instance_base.rb +0 -2
  13. data/lib/aasm/minitest.rb +5 -0
  14. data/lib/aasm/minitest/allow_event.rb +13 -0
  15. data/lib/aasm/minitest/allow_transition_to.rb +13 -0
  16. data/lib/aasm/minitest/have_state.rb +13 -0
  17. data/lib/aasm/minitest/transition_from.rb +21 -0
  18. data/lib/aasm/minitest_spec.rb +15 -0
  19. data/lib/aasm/persistence/active_record_persistence.rb +25 -101
  20. data/lib/aasm/persistence/base.rb +7 -3
  21. data/lib/aasm/persistence/mongoid_persistence.rb +15 -60
  22. data/lib/aasm/persistence/orm.rb +142 -0
  23. data/lib/aasm/persistence/redis_persistence.rb +16 -11
  24. data/lib/aasm/persistence/sequel_persistence.rb +36 -63
  25. data/lib/aasm/version.rb +1 -1
  26. data/lib/generators/active_record/templates/migration.rb +1 -1
  27. data/lib/generators/active_record/templates/migration_existing.rb +1 -1
  28. data/lib/motion-aasm.rb +2 -0
  29. data/spec/models/active_record/complex_active_record_example.rb +5 -1
  30. data/spec/models/guardian_without_from_specified.rb +18 -0
  31. data/spec/models/namespaced_multiple_example.rb +14 -0
  32. data/spec/models/redis/complex_redis_example.rb +40 -0
  33. data/spec/models/redis/redis_multiple.rb +20 -0
  34. data/spec/models/redis/redis_simple.rb +20 -0
  35. data/spec/models/sequel/complex_sequel_example.rb +4 -3
  36. data/spec/models/sequel/invalid_persistor.rb +52 -0
  37. data/spec/models/sequel/sequel_multiple.rb +13 -13
  38. data/spec/models/sequel/sequel_simple.rb +13 -12
  39. data/spec/models/sequel/silent_persistor.rb +50 -0
  40. data/spec/models/sequel/transactor.rb +112 -0
  41. data/spec/models/sequel/validator.rb +93 -0
  42. data/spec/models/sequel/worker.rb +12 -0
  43. data/spec/spec_helpers/redis.rb +8 -0
  44. data/spec/unit/guard_spec.rb +17 -0
  45. data/spec/unit/guard_without_from_specified_spec.rb +10 -0
  46. data/spec/unit/namespaced_multiple_example_spec.rb +22 -0
  47. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +4 -4
  48. data/spec/unit/persistence/active_record_persistence_spec.rb +4 -4
  49. data/spec/unit/persistence/redis_persistence_multiple_spec.rb +88 -0
  50. data/spec/unit/persistence/redis_persistence_spec.rb +5 -25
  51. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +2 -2
  52. data/spec/unit/persistence/sequel_persistence_spec.rb +275 -2
  53. data/test/minitest_helper.rb +57 -0
  54. data/test/unit/minitest_matcher_test.rb +80 -0
  55. metadata +35 -2
@@ -0,0 +1,93 @@
1
+ db = Sequel::DATABASES.first || Sequel.connect(SEQUEL_DB)
2
+
3
+ db.create_table(:validators) do
4
+ primary_key :id
5
+ String "name"
6
+ String "status"
7
+ Fixnum "worker_id"
8
+ end
9
+
10
+ module Sequel
11
+ class Validator < Sequel::Model(:validators)
12
+ plugin :validation_helpers
13
+
14
+ attr_accessor :after_all_transactions_performed,
15
+ :after_transaction_performed_on_fail,
16
+ :after_transaction_performed_on_run,
17
+ :before_all_transactions_performed,
18
+ :before_transaction_performed_on_fail,
19
+ :before_transaction_performed_on_run,
20
+ :invalid
21
+
22
+ def validate
23
+ super
24
+ errors.add(:validator, "invalid") if invalid
25
+ validates_presence :name
26
+ end
27
+
28
+ include AASM
29
+
30
+ aasm :column => :status, :whiny_persistence => true do
31
+ before_all_transactions :before_all_transactions
32
+ after_all_transactions :after_all_transactions
33
+
34
+ state :sleeping, :initial => true
35
+ state :running
36
+ state :failed, :after_enter => :fail
37
+
38
+ event :run, :after_commit => :change_name! do
39
+ after_transaction do
40
+ @after_transaction_performed_on_run = true
41
+ end
42
+
43
+ before_transaction do
44
+ @before_transaction_performed_on_run = true
45
+ end
46
+
47
+ transitions :to => :running, :from => :sleeping
48
+ end
49
+
50
+ event :sleep do
51
+ after_commit do |name|
52
+ change_name_on_sleep name
53
+ end
54
+ transitions :to => :sleeping, :from => :running
55
+ end
56
+
57
+ event :fail do
58
+ after_transaction do
59
+ @after_transaction_performed_on_fail = true
60
+ end
61
+
62
+ before_transaction do
63
+ @before_transaction_performed_on_fail = true
64
+ end
65
+
66
+ transitions :to => :failed, :from => [:sleeping, :running]
67
+ end
68
+ end
69
+
70
+ def change_name!
71
+ self.name = "name changed"
72
+ save(raise_on_failure: true)
73
+ end
74
+
75
+ def change_name_on_sleep name
76
+ self.name = name
77
+ save(raise_on_failure: true)
78
+ end
79
+
80
+ def fail
81
+ raise StandardError.new('failed on purpose')
82
+ end
83
+
84
+ def after_all_transactions
85
+ @after_all_transactions_performed = true
86
+ end
87
+
88
+ def before_all_transactions
89
+ @before_all_transactions_performed = true
90
+ end
91
+ end
92
+
93
+ end
@@ -0,0 +1,12 @@
1
+ db = Sequel::DATABASES.first || Sequel.connect(SEQUEL_DB)
2
+
3
+ db.create_table(:workers) do
4
+ primary_key :id
5
+ String "name"
6
+ String "status"
7
+ end
8
+
9
+ module Sequel
10
+ class Worker < Sequel::Model(:workers)
11
+ end
12
+ end
@@ -2,6 +2,14 @@
2
2
  begin
3
3
  require 'redis-objects'
4
4
  puts "redis-objects gem found, running Redis specs \e[32m#{'✔'}\e[0m"
5
+
6
+ Redis.current = Redis.new(host: '127.0.0.1', port: 6379)
7
+
8
+ RSpec.configure do |c|
9
+ c.before(:each) do
10
+ Redis.current.keys('redis_*').each { |k| Redis.current.del k }
11
+ end
12
+ end
5
13
  rescue LoadError
6
14
  puts "redis-objects gem not found, not running Redis specs \e[31m#{'✖'}\e[0m"
7
15
  end
@@ -70,3 +70,20 @@ describe "event guards" do
70
70
  end
71
71
 
72
72
  end
73
+
74
+ if defined?(ActiveRecord)
75
+
76
+ Dir[File.dirname(__FILE__) + "/../models/active_record/*.rb"].sort.each do |f|
77
+ require File.expand_path(f)
78
+ end
79
+
80
+ load_schema
81
+
82
+ describe "ActiveRecord per-transition guards" do
83
+ let(:example) { ComplexActiveRecordExample.new }
84
+
85
+ it "should be able to increment" do
86
+ expect(example.may_increment?).to be true
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe "transitions without from specified" do
4
+ let(:guardian) { GuardianWithoutFromSpecified.new }
5
+
6
+ it "allows the transitions if guard succeeds" do
7
+ expect { guardian.use_guards_where_the_first_fails! }.to_not raise_error
8
+ expect(guardian).to be_gamma
9
+ end
10
+ end
@@ -11,6 +11,10 @@ describe 'state machine' do
11
11
  expect(namespaced.aasm(:review_status).current_state).to eq(:unapproved)
12
12
  expect(namespaced).to respond_to(:review_unapproved?)
13
13
  expect(namespaced).to be_review_unapproved
14
+
15
+ expect(namespaced.aasm(:car).current_state).to eq(:unsold)
16
+ expect(namespaced).to respond_to(:car_unsold?)
17
+ expect(namespaced).to be_car_unsold
14
18
  end
15
19
 
16
20
  it 'allows transitions to other states' do
@@ -25,6 +29,12 @@ describe 'state machine' do
25
29
  namespaced.approve_review!
26
30
  expect(namespaced).to respond_to(:review_approved?)
27
31
  expect(namespaced).to be_review_approved
32
+
33
+ expect(namespaced).to respond_to(:sell_car)
34
+ expect(namespaced).to respond_to(:sell_car!)
35
+ namespaced.sell_car!
36
+ expect(namespaced).to respond_to(:car_sold?)
37
+ expect(namespaced).to be_car_sold
28
38
  end
29
39
 
30
40
  it 'denies transitions to other states' do
@@ -41,6 +51,13 @@ describe 'state machine' do
41
51
  expect {namespaced.approve_review}.to raise_error(AASM::InvalidTransition)
42
52
  expect {namespaced.approve_review!}.to raise_error(AASM::InvalidTransition)
43
53
  namespaced.unapprove_review
54
+
55
+ expect {namespaced.return_car}.to raise_error(AASM::InvalidTransition)
56
+ expect {namespaced.return_car!}.to raise_error(AASM::InvalidTransition)
57
+ namespaced.sell_car
58
+ expect {namespaced.sell_car}.to raise_error(AASM::InvalidTransition)
59
+ expect {namespaced.sell_car!}.to raise_error(AASM::InvalidTransition)
60
+ namespaced.return_car
44
61
  end
45
62
 
46
63
  it 'defines constants for each state name' do
@@ -49,5 +66,10 @@ describe 'state machine' do
49
66
 
50
67
  expect(NamespacedMultipleExample::STATE_REVIEW_UNAPPROVED).to eq(:unapproved)
51
68
  expect(NamespacedMultipleExample::STATE_REVIEW_APPROVED).to eq(:approved)
69
+
70
+ expect(NamespacedMultipleExample::STATE_CAR_UNSOLD).to eq(:unsold)
71
+ expect(NamespacedMultipleExample::STATE_CAR_SOLD).to eq(:sold)
52
72
  end
73
+
74
+
53
75
  end
@@ -139,7 +139,7 @@ if defined?(ActiveRecord)
139
139
 
140
140
  before :each do
141
141
  allow(gate).to receive(:aasm_enum).and_return(enum_name)
142
- allow(gate).to receive(:aasm_write_attribute)
142
+ allow(gate).to receive(:aasm_write_state_attribute)
143
143
  allow(gate).to receive(:write_attribute)
144
144
 
145
145
  allow(MultipleGate).to receive(enum_name).and_return(enum)
@@ -173,7 +173,7 @@ if defined?(ActiveRecord)
173
173
 
174
174
  gate.aasm_write_state state_sym, :left
175
175
 
176
- expect(gate).to have_received(:aasm_write_attribute).with(state_sym, :left)
176
+ expect(gate).to have_received(:aasm_write_state_attribute).with(state_sym, :left)
177
177
  expect(gate).to_not have_received :write_attribute
178
178
  end
179
179
  end
@@ -183,7 +183,7 @@ if defined?(ActiveRecord)
183
183
  it "delegates state update to the helper method" do
184
184
  gate.aasm_write_state_without_persistence state_sym, :left
185
185
 
186
- expect(gate).to have_received(:aasm_write_attribute).with(state_sym, :left)
186
+ expect(gate).to have_received(:aasm_write_state_attribute).with(state_sym, :left)
187
187
  expect(gate).to_not have_received :write_attribute
188
188
  end
189
189
  end
@@ -219,7 +219,7 @@ if defined?(ActiveRecord)
219
219
  allow(gate).to receive(:write_attribute)
220
220
  allow(gate).to receive(:aasm_raw_attribute_value).and_return(value)
221
221
 
222
- gate.send(:aasm_write_attribute, sym, :left)
222
+ gate.send(:aasm_write_state_attribute, sym, :left)
223
223
  end
224
224
 
225
225
  it "generates attribute value using a helper method" do
@@ -139,7 +139,7 @@ if defined?(ActiveRecord)
139
139
 
140
140
  before :each do
141
141
  allow(gate).to receive(:aasm_enum).and_return(enum_name)
142
- allow(gate).to receive(:aasm_write_attribute)
142
+ allow(gate).to receive(:aasm_write_state_attribute)
143
143
  allow(gate).to receive(:write_attribute)
144
144
 
145
145
  allow(Gate).to receive(enum_name).and_return(enum)
@@ -191,7 +191,7 @@ if defined?(ActiveRecord)
191
191
 
192
192
  gate.aasm_write_state state_sym
193
193
 
194
- expect(gate).to have_received(:aasm_write_attribute).with(state_sym, :default)
194
+ expect(gate).to have_received(:aasm_write_state_attribute).with(state_sym, :default)
195
195
  expect(gate).to_not have_received :write_attribute
196
196
  end
197
197
  end
@@ -201,7 +201,7 @@ if defined?(ActiveRecord)
201
201
  it "delegates state update to the helper method" do
202
202
  gate.aasm_write_state_without_persistence state_sym
203
203
 
204
- expect(gate).to have_received(:aasm_write_attribute).with(state_sym, :default)
204
+ expect(gate).to have_received(:aasm_write_state_attribute).with(state_sym, :default)
205
205
  expect(gate).to_not have_received :write_attribute
206
206
  end
207
207
  end
@@ -237,7 +237,7 @@ if defined?(ActiveRecord)
237
237
  allow(gate).to receive(:write_attribute)
238
238
  allow(gate).to receive(:aasm_raw_attribute_value).and_return(value)
239
239
 
240
- gate.send(:aasm_write_attribute, sym)
240
+ gate.send(:aasm_write_state_attribute, sym)
241
241
  end
242
242
 
243
243
  it "generates attribute value using a helper method" do
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ if defined?(Redis)
4
+ describe 'redis' do
5
+
6
+ Dir[File.dirname(__FILE__) + "/../../models/redis/*.rb"].sort.each do |f|
7
+ require File.expand_path(f)
8
+ end
9
+
10
+ before(:all) do
11
+ @model = RedisMultiple
12
+ end
13
+
14
+ describe "instance methods" do
15
+ let(:model) {@model.new}
16
+
17
+ it "should respond to aasm persistence methods" do
18
+ expect(model).to respond_to(:aasm_read_state)
19
+ expect(model).to respond_to(:aasm_write_state)
20
+ expect(model).to respond_to(:aasm_write_state_without_persistence)
21
+ end
22
+
23
+ it "should return the initial state when new and the aasm field is nil" do
24
+ expect(model.aasm(:left).current_state).to eq(:alpha)
25
+ end
26
+
27
+ it "should save the initial state" do
28
+ expect(model.status).to eq("alpha")
29
+ end
30
+
31
+ it "should return the aasm column the aasm field is not nil" do
32
+ model.status = "beta"
33
+ expect(model.aasm(:left).current_state).to eq(:beta)
34
+ end
35
+
36
+ it "should allow a nil state" do
37
+ model.status = nil
38
+ expect(model.aasm(:left).current_state).to be_nil
39
+ end
40
+ end
41
+
42
+ describe 'subclasses' do
43
+ it "should have the same states as its parent class" do
44
+ expect(Class.new(@model).aasm(:left).states).to eq(@model.aasm(:left).states)
45
+ end
46
+
47
+ it "should have the same events as its parent class" do
48
+ expect(Class.new(@model).aasm(:left).events).to eq(@model.aasm(:left).events)
49
+ end
50
+
51
+ it "should have the same column as its parent even for the new dsl" do
52
+ expect(@model.aasm(:left).attribute_name).to eq(:status)
53
+ expect(Class.new(@model).aasm(:left).attribute_name).to eq(:status)
54
+ end
55
+ end
56
+
57
+ describe "complex example" do
58
+ it "works" do
59
+ record = RedisComplexExample.new
60
+
61
+ expect(record.aasm(:left).current_state).to eql :one
62
+ expect(record.aasm(:right).current_state).to eql :alpha
63
+
64
+ expect_aasm_states record, :one, :alpha
65
+
66
+ record.increment!
67
+ expect_aasm_states record, :two, :alpha
68
+
69
+ record.level_up!
70
+ expect_aasm_states record, :two, :beta
71
+
72
+ record.increment!
73
+ expect { record.increment! }.to raise_error(AASM::InvalidTransition)
74
+ expect_aasm_states record, :three, :beta
75
+
76
+ record.level_up!
77
+ expect_aasm_states record, :three, :gamma
78
+ end
79
+
80
+ def expect_aasm_states(record, left_state, right_state)
81
+ expect(record.aasm(:left).current_state).to eql left_state.to_sym
82
+ expect(record.left.value.to_s).to eql left_state.to_s
83
+ expect(record.aasm(:right).current_state).to eql right_state.to_sym
84
+ expect(record.right.value.to_s).to eql right_state.to_s
85
+ end
86
+ end
87
+ end
88
+ end
@@ -3,31 +3,12 @@ require 'spec_helper'
3
3
  if defined?(Redis::Objects)
4
4
  describe 'redis' do
5
5
 
6
- before(:all) do
7
- Redis.current = Redis.new(host: '127.0.0.1', port: 6379)
8
-
9
- @model = Class.new do
10
- attr_accessor :default
11
-
12
- include Redis::Objects
13
- include AASM
14
-
15
- value :status
16
-
17
- def id
18
- 1
19
- end
6
+ Dir[File.dirname(__FILE__) + "/../../models/redis/*.rb"].sort.each do |f|
7
+ require File.expand_path(f)
8
+ end
20
9
 
21
- aasm column: :status
22
- aasm do
23
- state :alpha, initial: true
24
- state :beta
25
- state :gamma
26
- event :release do
27
- transitions from: [:alpha, :beta, :gamma], to: :beta
28
- end
29
- end
30
- end
10
+ before(:all) do
11
+ @model = RedisSimple
31
12
  end
32
13
 
33
14
  describe "instance methods" do
@@ -68,6 +49,5 @@ if defined?(Redis::Objects)
68
49
  expect(Class.new(@model).aasm.attribute_name).to eq(:status)
69
50
  end
70
51
  end
71
-
72
52
  end
73
53
  end
@@ -8,7 +8,7 @@ if defined?(Sequel)
8
8
  end
9
9
 
10
10
  before(:all) do
11
- @model = SequelMultiple
11
+ @model = Sequel::Multiple
12
12
  end
13
13
 
14
14
  describe "instance methods" do
@@ -93,7 +93,7 @@ if defined?(Sequel)
93
93
 
94
94
  describe "complex example" do
95
95
  it "works" do
96
- record = ComplexSequelExample.new
96
+ record = Sequel::ComplexExample.new
97
97
  expect(record.aasm(:left).current_state).to eql :one
98
98
  expect(record.left).to be_nil
99
99
  expect(record.aasm(:right).current_state).to eql :alpha
@@ -1,5 +1,4 @@
1
1
  require 'spec_helper'
2
-
3
2
  if defined?(Sequel)
4
3
  describe 'sequel' do
5
4
 
@@ -8,7 +7,7 @@ if defined?(Sequel)
8
7
  end
9
8
 
10
9
  before(:all) do
11
- @model = SequelSimple
10
+ @model = Sequel::Simple
12
11
  end
13
12
 
14
13
  describe "instance methods" do
@@ -91,5 +90,279 @@ if defined?(Sequel)
91
90
  end
92
91
  end
93
92
 
93
+ describe 'transitions with persistence' do
94
+
95
+ it "should work for valid models" do
96
+ valid_object = Sequel::Validator.create(:name => 'name')
97
+ expect(valid_object).to be_sleeping
98
+ valid_object.status = :running
99
+ expect(valid_object).to be_running
100
+ end
101
+
102
+ it 'should not store states for invalid models' do
103
+ validator = Sequel::Validator.create(:name => 'name')
104
+ expect(validator).to be_valid
105
+ expect(validator).to be_sleeping
106
+
107
+ validator.name = nil
108
+ expect(validator).not_to be_valid
109
+ expect { validator.run! }.to raise_error(Sequel::ValidationFailed)
110
+ expect(validator).to be_sleeping
111
+
112
+ validator.reload
113
+ expect(validator).not_to be_running
114
+ expect(validator).to be_sleeping
115
+
116
+ validator.name = 'another name'
117
+ expect(validator).to be_valid
118
+ expect(validator.run!).to be_truthy
119
+ expect(validator).to be_running
120
+
121
+ validator.reload
122
+ expect(validator).to be_running
123
+ expect(validator).not_to be_sleeping
124
+ end
125
+
126
+ it 'should not store states for invalid models silently if configured' do
127
+ validator = Sequel::SilentPersistor.create(:name => 'name')
128
+ expect(validator).to be_valid
129
+ expect(validator).to be_sleeping
130
+
131
+ validator.name = nil
132
+ expect(validator).not_to be_valid
133
+ expect(validator.run!).to be_falsey
134
+ expect(validator).to be_sleeping
135
+
136
+ validator.reload
137
+ expect(validator).not_to be_running
138
+ expect(validator).to be_sleeping
139
+
140
+ validator.name = 'another name'
141
+ expect(validator).to be_valid
142
+ expect(validator.run!).to be_truthy
143
+ expect(validator).to be_running
144
+
145
+ validator.reload
146
+ expect(validator).to be_running
147
+ expect(validator).not_to be_sleeping
148
+ end
149
+
150
+ it 'should store states for invalid models if configured' do
151
+ persistor = Sequel::InvalidPersistor.create(:name => 'name')
152
+ expect(persistor).to be_valid
153
+ expect(persistor).to be_sleeping
154
+
155
+ persistor.name = nil
156
+ expect(persistor).not_to be_valid
157
+ expect(persistor.run!).to be_truthy
158
+ expect(persistor).to be_running
159
+
160
+ persistor = Sequel::InvalidPersistor[persistor.id]
161
+ persistor.valid?
162
+ expect(persistor).to be_valid
163
+ expect(persistor).to be_running
164
+ expect(persistor).not_to be_sleeping
165
+
166
+ persistor.reload
167
+ expect(persistor).to be_running
168
+ expect(persistor).not_to be_sleeping
169
+ end
170
+
171
+ describe 'pessimistic locking' do
172
+ let(:worker) { Sequel::Worker.create(:name => 'worker', :status => 'sleeping') }
173
+
174
+ subject { transactor.run! }
175
+
176
+ context 'no lock' do
177
+ let(:transactor) { Sequel::NoLockTransactor.create(:name => 'no_lock_transactor', :worker => worker) }
178
+
179
+ it 'should not invoke lock!' do
180
+ expect(transactor).to_not receive(:lock!)
181
+ subject
182
+ end
183
+ end
184
+
185
+ context 'a default lock' do
186
+ let(:transactor) { Sequel::LockTransactor.create(:name => 'lock_transactor', :worker => worker) }
187
+
188
+ it 'should invoke lock!' do
189
+ expect(transactor).to receive(:lock!).and_call_original
190
+ subject
191
+ end
192
+ end
193
+
194
+ context 'a FOR UPDATE NOWAIT lock' do
195
+ let(:transactor) { Sequel::LockNoWaitTransactor.create(:name => 'lock_no_wait_transactor', :worker => worker) }
196
+
197
+ it 'should invoke lock! with FOR UPDATE NOWAIT' do
198
+ # TODO: With and_call_original, get an error with syntax, should look into it.
199
+ expect(transactor).to receive(:lock!).with('FOR UPDATE NOWAIT')# .and_call_original
200
+ subject
201
+ end
202
+ end
203
+ end
204
+
205
+ describe 'transactions' do
206
+ let(:worker) { Sequel::Worker.create(:name => 'worker', :status => 'sleeping') }
207
+ let(:transactor) { Sequel::Transactor.create(:name => 'transactor', :worker => worker) }
208
+
209
+ it 'should rollback all changes' do
210
+ expect(transactor).to be_sleeping
211
+ expect(worker.status).to eq('sleeping')
212
+
213
+ expect {transactor.run!}.to raise_error(StandardError, 'failed on purpose')
214
+ expect(transactor).to be_running
215
+ expect(worker.reload.status).to eq('sleeping')
216
+ end
217
+
218
+ context "nested transactions" do
219
+ it "should rollback all changes in nested transaction" do
220
+ expect(transactor).to be_sleeping
221
+ expect(worker.status).to eq('sleeping')
222
+
223
+ Sequel::Worker.db.transaction do
224
+ expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
225
+ end
226
+
227
+ expect(transactor).to be_running
228
+ expect(worker.reload.status).to eq('sleeping')
229
+ end
230
+
231
+ it "should only rollback changes in the main transaction not the nested one" do
232
+ # change configuration to not require new transaction
233
+ AASM::StateMachineStore[Sequel::Transactor][:default].config.requires_new_transaction = false
234
+
235
+ expect(transactor).to be_sleeping
236
+ expect(worker.status).to eq('sleeping')
237
+ Sequel::Worker.db.transaction do
238
+ expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
239
+ end
240
+ expect(transactor).to be_running
241
+ expect(worker.reload.status).to eq('running')
242
+ end
243
+ end
244
+
245
+ describe "after_commit callback" do
246
+ it "should fire :after_commit if transaction was successful" do
247
+ validator = Sequel::Validator.create(:name => 'name')
248
+ expect(validator).to be_sleeping
249
+
250
+ validator.run!
251
+ expect(validator).to be_running
252
+ expect(validator.name).to eq("name changed")
253
+
254
+ validator.sleep!("sleeper")
255
+ expect(validator).to be_sleeping
256
+ expect(validator.name).to eq("sleeper")
257
+ end
258
+
259
+ it "should not fire :after_commit if transaction failed" do
260
+ validator = Sequel::Validator.create(:name => 'name')
261
+ expect { validator.fail! }.to raise_error(StandardError, 'failed on purpose')
262
+ expect(validator.name).to eq("name")
263
+ end
264
+
265
+ it "should not fire :after_commit if validation failed when saving object" do
266
+ validator = Sequel::Validator.create(:name => 'name')
267
+ validator.invalid = true
268
+ expect { validator.run! }.to raise_error(Sequel::ValidationFailed, 'validator invalid')
269
+ expect(validator).to be_sleeping
270
+ expect(validator.name).to eq("name")
271
+ end
272
+
273
+ it "should not fire if not saving" do
274
+ validator = Sequel::Validator.create(:name => 'name')
275
+ expect(validator).to be_sleeping
276
+ validator.run
277
+ expect(validator).to be_running
278
+ expect(validator.name).to eq("name")
279
+ end
280
+ end
281
+
282
+ describe 'before and after transaction callbacks' do
283
+ [:after, :before].each do |event_type|
284
+ describe "#{event_type}_transaction callback" do
285
+ it "should fire :#{event_type}_transaction if transaction was successful" do
286
+ validator = Sequel::Validator.create(:name => 'name')
287
+ expect(validator).to be_sleeping
288
+
289
+ expect { validator.run! }.to change { validator.send("#{event_type}_transaction_performed_on_run") }.from(nil).to(true)
290
+ expect(validator).to be_running
291
+ end
292
+
293
+ it "should fire :#{event_type}_transaction if transaction failed" do
294
+ validator = Sequel::Validator.create(:name => 'name')
295
+ expect do
296
+ begin
297
+ validator.fail!
298
+ rescue => ignored
299
+ end
300
+ end.to change { validator.send("#{event_type}_transaction_performed_on_fail") }.from(nil).to(true)
301
+ expect(validator).to_not be_running
302
+ end
303
+
304
+ it "should not fire :#{event_type}_transaction if not saving" do
305
+ validator = Sequel::Validator.create(:name => 'name')
306
+ expect(validator).to be_sleeping
307
+ expect { validator.run }.to_not change { validator.send("#{event_type}_transaction_performed_on_run") }
308
+ expect(validator).to be_running
309
+ expect(validator.name).to eq("name")
310
+ end
311
+ end
312
+ end
313
+ end
314
+
315
+ describe 'before and after all transactions callbacks' do
316
+ [:after, :before].each do |event_type|
317
+ describe "#{event_type}_all_transactions callback" do
318
+ it "should fire :#{event_type}_all_transactions if transaction was successful" do
319
+ validator = Sequel::Validator.create(:name => 'name')
320
+ expect(validator).to be_sleeping
321
+
322
+ expect { validator.run! }.to change { validator.send("#{event_type}_all_transactions_performed") }.from(nil).to(true)
323
+ expect(validator).to be_running
324
+ end
325
+
326
+ it "should fire :#{event_type}_all_transactions if transaction failed" do
327
+ validator = Sequel::Validator.create(:name => 'name')
328
+ expect do
329
+ begin
330
+ validator.fail!
331
+ rescue => ignored
332
+ end
333
+ end.to change { validator.send("#{event_type}_all_transactions_performed") }.from(nil).to(true)
334
+ expect(validator).to_not be_running
335
+ end
336
+
337
+ it "should not fire :#{event_type}_all_transactions if not saving" do
338
+ validator = Sequel::Validator.create(:name => 'name')
339
+ expect(validator).to be_sleeping
340
+ expect { validator.run }.to_not change { validator.send("#{event_type}_all_transactions_performed") }
341
+ expect(validator).to be_running
342
+ expect(validator.name).to eq("name")
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ context "when not persisting" do
349
+ it 'should not rollback all changes' do
350
+ expect(transactor).to be_sleeping
351
+ expect(worker.status).to eq('sleeping')
352
+
353
+ # Notice here we're calling "run" and not "run!" with a bang.
354
+ expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
355
+ expect(transactor).to be_running
356
+ expect(worker.reload.status).to eq('running')
357
+ end
358
+
359
+ it 'should not create a database transaction' do
360
+ expect(transactor.class).not_to receive(:transaction)
361
+ expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
362
+ end
363
+ end
364
+ end
365
+ end
366
+
94
367
  end
95
368
  end