aasm 4.0.3 → 4.1.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -11
  3. data/CHANGELOG.md +28 -1
  4. data/Gemfile +6 -2
  5. data/README.md +31 -12
  6. data/README_FROM_VERSION_3_TO_4.md +1 -1
  7. data/aasm.gemspec +9 -10
  8. data/gemfiles/rails_3.2.gemfile +3 -1
  9. data/gemfiles/rails_4.0.gemfile +2 -5
  10. data/gemfiles/rails_4.0_bson1.gemfile +14 -0
  11. data/gemfiles/rails_4.1.gemfile +2 -5
  12. data/gemfiles/rails_4.1_bson1.gemfile +14 -0
  13. data/gemfiles/rails_4.2.gemfile +13 -0
  14. data/gemfiles/rails_4.2_bson1.gemfile +14 -0
  15. data/lib/aasm/aasm.rb +1 -19
  16. data/lib/aasm/base.rb +8 -8
  17. data/lib/aasm/{event.rb → core/event.rb} +13 -5
  18. data/lib/aasm/{state.rb → core/state.rb} +1 -1
  19. data/lib/aasm/{transition.rb → core/transition.rb} +1 -3
  20. data/lib/aasm/instance_base.rb +1 -1
  21. data/lib/aasm/persistence/active_record_persistence.rb +14 -13
  22. data/lib/aasm/persistence/base.rb +4 -1
  23. data/lib/aasm/persistence/mongo_mapper_persistence.rb +174 -0
  24. data/lib/aasm/persistence/mongoid_persistence.rb +5 -7
  25. data/lib/aasm/persistence/plain_persistence.rb +24 -0
  26. data/lib/aasm/persistence/sequel_persistence.rb +2 -0
  27. data/lib/aasm/persistence.rb +19 -11
  28. data/lib/aasm/state_machine.rb +1 -1
  29. data/lib/aasm/version.rb +1 -1
  30. data/lib/aasm.rb +13 -15
  31. data/spec/database.rb +5 -1
  32. data/spec/models/callbacks/basic.rb +75 -0
  33. data/spec/models/callbacks/guard_within_block.rb +66 -0
  34. data/spec/models/callbacks/private_method.rb +44 -0
  35. data/spec/models/callbacks/with_args.rb +61 -0
  36. data/spec/models/callbacks/with_state_args.rb +26 -0
  37. data/spec/models/mongo_mapper/no_scope_mongo_mapper.rb +10 -0
  38. data/spec/models/mongo_mapper/simple_mongo_mapper.rb +11 -0
  39. data/spec/models/mongo_mapper/simple_new_dsl_mongo_mapper.rb +12 -0
  40. data/spec/models/mongoid/simple_new_dsl_mongoid.rb +1 -1
  41. data/spec/models/persistence.rb +79 -1
  42. data/spec/spec_helper.rb +2 -1
  43. data/spec/unit/callbacks_spec.rb +101 -62
  44. data/spec/unit/complex_example_spec.rb +9 -9
  45. data/spec/unit/event_spec.rb +12 -12
  46. data/spec/unit/inspection_spec.rb +2 -2
  47. data/spec/unit/memory_leak_spec.rb +12 -12
  48. data/spec/unit/persistence/active_record_persistence_spec.rb +58 -72
  49. data/spec/unit/persistence/mongo_mapper_persistance_spec.rb +135 -0
  50. data/spec/unit/persistence/mongoid_persistance_spec.rb +4 -10
  51. data/spec/unit/persistence/sequel_persistence_spec.rb +2 -1
  52. data/spec/unit/state_spec.rb +2 -2
  53. data/spec/unit/subclassing_spec.rb +1 -1
  54. data/spec/unit/transition_spec.rb +33 -33
  55. metadata +32 -40
  56. data/spec/models/callback_new_dsl.rb +0 -129
  57. data/spec/models/guard_within_block.rb +0 -64
@@ -30,15 +30,15 @@ describe "instance methods" do
30
30
  let(:columns_hash) { Hash[column_name, column] }
31
31
 
32
32
  before :each do
33
- gate.class.aasm.stub(:attribute_name).and_return(column_name.to_sym)
34
- gate.class.stub(:columns_hash).and_return(columns_hash)
33
+ allow(gate.class.aasm).to receive(:attribute_name).and_return(column_name.to_sym)
34
+ allow(gate.class).to receive(:columns_hash).and_return(columns_hash)
35
35
  end
36
36
 
37
37
  context "when AASM column has integer type" do
38
38
  let(:column) { double(Object, type: :integer) }
39
39
 
40
40
  it "returns true" do
41
- expect(subject.call).to be_true
41
+ expect(subject.call).to be_truthy
42
42
  end
43
43
  end
44
44
 
@@ -46,7 +46,7 @@ describe "instance methods" do
46
46
  let(:column) { double(Object, type: :string) }
47
47
 
48
48
  it "returns false" do
49
- expect(subject.call).to be_false
49
+ expect(subject.call).to be_falsey
50
50
  end
51
51
  end
52
52
  end
@@ -55,7 +55,7 @@ describe "instance methods" do
55
55
  subject { lambda{ gate.send(:aasm_guess_enum_method) } }
56
56
 
57
57
  before :each do
58
- gate.class.aasm.stub(:attribute_name).and_return(:value)
58
+ allow(gate.class.aasm).to receive(:attribute_name).and_return(:value)
59
59
  end
60
60
 
61
61
  it "pluralizes AASM column name" do
@@ -64,69 +64,56 @@ describe "instance methods" do
64
64
  end
65
65
 
66
66
  describe "aasm_enum" do
67
- subject { lambda{ gate.send(:aasm_enum) } }
68
-
69
67
  context "when AASM enum setting contains an explicit enum method name" do
70
- let(:enum) { :test }
71
-
72
- before :each do
73
- AASM::StateMachine[Gate].config.stub(:enum).and_return(enum)
74
- end
68
+ let(:with_enum) { WithEnum.new }
75
69
 
76
70
  it "returns whatever value was set in AASM config" do
77
- expect(subject.call).to eq enum
71
+ expect(with_enum.send(:aasm_enum)).to eq :test
78
72
  end
79
73
  end
80
74
 
81
75
  context "when AASM enum setting is simply set to true" do
76
+ let(:with_true_enum) { WithTrueEnum.new }
82
77
  before :each do
83
- AASM::StateMachine[Gate].config.stub(:enum).and_return(true)
84
- Gate.aasm.stub(:attribute_name).and_return(:value)
85
- gate.stub(:aasm_guess_enum_method).and_return(:values)
78
+ allow(WithTrueEnum.aasm).to receive(:attribute_name).and_return(:value)
86
79
  end
87
80
 
88
81
  it "infers enum method name from pluralized column name" do
89
- expect(subject.call).to eq :values
90
- expect(gate).to have_received :aasm_guess_enum_method
82
+ expect(with_true_enum.send(:aasm_enum)).to eq :values
91
83
  end
92
84
  end
93
85
 
94
86
  context "when AASM enum setting is explicitly disabled" do
95
- before :each do
96
- AASM::StateMachine[Gate].config.stub(:enum).and_return(false)
97
- end
87
+ let(:with_false_enum) { WithFalseEnum.new }
98
88
 
99
89
  it "returns nil" do
100
- expect(subject.call).to be_nil
90
+ expect(with_false_enum.send(:aasm_enum)).to be_nil
101
91
  end
102
92
  end
103
93
 
104
94
  context "when AASM enum setting is not enabled" do
105
95
  before :each do
106
- AASM::StateMachine[Gate].config.stub(:enum).and_return(nil)
107
- Gate.aasm.stub(:attribute_name).and_return(:value)
96
+ allow(Gate.aasm).to receive(:attribute_name).and_return(:value)
108
97
  end
109
98
 
110
99
  context "when AASM column looks like enum" do
111
100
  before :each do
112
- gate.stub(:aasm_column_looks_like_enum).and_return(true)
113
- gate.stub(:aasm_guess_enum_method).and_return(:values)
101
+ allow(gate).to receive(:aasm_column_looks_like_enum).and_return(true)
114
102
  end
115
103
 
116
104
  it "infers enum method name from pluralized column name" do
117
- expect(subject.call).to eq :values
118
- expect(gate).to have_received :aasm_guess_enum_method
105
+ expect(gate.send(:aasm_enum)).to eq :values
119
106
  end
120
107
  end
121
108
 
122
109
  context "when AASM column doesn't look like enum'" do
123
110
  before :each do
124
- gate.stub(:aasm_column_looks_like_enum)
111
+ allow(gate).to receive(:aasm_column_looks_like_enum)
125
112
  .and_return(false)
126
113
  end
127
114
 
128
115
  it "returns nil, as we're not using enum" do
129
- expect(subject.call).to be_nil
116
+ expect(gate.send(:aasm_enum)).to be_nil
130
117
  end
131
118
  end
132
119
  end
@@ -139,24 +126,17 @@ describe "instance methods" do
139
126
  let(:enum) { Hash[state_sym, state_code] }
140
127
 
141
128
  before :each do
142
- gate
143
- .stub(:aasm_enum)
144
- .and_return(enum_name)
145
- gate.stub(:aasm_write_attribute)
146
- gate.stub(:write_attribute)
147
-
148
- gate
149
- .class
150
- .stub(enum_name)
151
- .and_return(enum)
129
+ allow(gate).to receive(:aasm_enum).and_return(enum_name)
130
+ allow(gate).to receive(:aasm_write_attribute)
131
+ allow(gate).to receive(:write_attribute)
132
+
133
+ allow(Gate).to receive(enum_name).and_return(enum)
152
134
  end
153
135
 
154
136
  describe "aasm_write_state" do
155
137
  context "when AASM is configured to skip validations on save" do
156
138
  before :each do
157
- gate
158
- .stub(:aasm_skipping_validations)
159
- .and_return(true)
139
+ allow(gate).to receive(:aasm_skipping_validations).and_return(true)
160
140
  end
161
141
 
162
142
  it "passes state code instead of state symbol to update_all" do
@@ -164,10 +144,7 @@ describe "instance methods" do
164
144
  # parameters in the middle of the chain, so we need to use
165
145
  # intermediate object instead.
166
146
  obj = double(Object, update_all: 1)
167
- gate
168
- .class
169
- .stub(:where)
170
- .and_return(obj)
147
+ allow(Gate).to receive(:where).and_return(obj)
171
148
 
172
149
  gate.aasm_write_state state_sym
173
150
 
@@ -179,7 +156,7 @@ describe "instance methods" do
179
156
  context "when AASM is not skipping validations" do
180
157
  it "delegates state update to the helper method" do
181
158
  # Let's pretend that validation is passed
182
- gate.stub(:save).and_return(true)
159
+ allow(gate).to receive(:save).and_return(true)
183
160
 
184
161
  gate.aasm_write_state state_sym
185
162
 
@@ -210,9 +187,7 @@ describe "instance methods" do
210
187
  let(:state_sym) { :running }
211
188
 
212
189
  before :each do
213
- gate
214
- .stub(:aasm_enum)
215
- .and_return(nil)
190
+ allow(gate).to receive(:aasm_enum).and_return(nil)
216
191
  end
217
192
 
218
193
  describe "aasm_raw_attribute_value" do
@@ -228,9 +203,8 @@ describe "instance methods" do
228
203
  let(:value) { 42 }
229
204
 
230
205
  before :each do
231
- gate.stub(:write_attribute)
232
- gate.stub(:aasm_raw_attribute_value)
233
- .and_return(value)
206
+ allow(gate).to receive(:write_attribute)
207
+ allow(gate).to receive(:aasm_raw_attribute_value).and_return(value)
234
208
 
235
209
  gate.send(:aasm_write_attribute, sym)
236
210
  end
@@ -265,24 +239,36 @@ describe "instance methods" do
265
239
  expect(gate.aasm.current_state).to be_nil
266
240
  end
267
241
 
268
- it "should call aasm_ensure_initial_state on validation before create" do
269
- expect(gate).to receive(:aasm_ensure_initial_state).and_return(true)
270
- gate.valid?
271
- end
242
+ context 'on initialization' do
243
+ it "should initialize the aasm state" do
244
+ expect(Gate.new.aasm_state).to eql 'opened'
245
+ expect(Gate.new.aasm.current_state).to eql :opened
246
+ end
272
247
 
273
- it "should call aasm_ensure_initial_state before create, even if skipping validations" do
274
- expect(gate).to receive(:aasm_ensure_initial_state).and_return(true)
275
- gate.save(:validate => false)
276
- end
248
+ it "should not initialize the aasm state if it has not been loaded" do
249
+ # we have to create a gate in the database, for which we only want to
250
+ # load the id, and not the state
251
+ gate = Gate.create!
277
252
 
278
- it "should not call aasm_ensure_initial_state on validation before update" do
279
- allow(gate).to receive(:new_record?).and_return(false)
280
- expect(gate).not_to receive(:aasm_ensure_initial_state)
281
- gate.valid?
253
+ # then we just load the gate ids
254
+ Gate.select(:id).where(id: gate.id).first
255
+ end
282
256
  end
283
257
 
284
258
  end
285
259
 
260
+ if ActiveRecord::VERSION::MAJOR < 4 && ActiveRecord::VERSION::MINOR < 2 # won't work with Rails >= 4.2
261
+ describe "direct state column access" do
262
+ it "accepts false states" do
263
+ f = FalseState.create!
264
+ expect(f.aasm_state).to eql false
265
+ expect {
266
+ f.aasm.events.map(&:name)
267
+ }.to_not raise_error
268
+ end
269
+ end
270
+ end
271
+
286
272
  describe 'subclasses' do
287
273
  it "should have the same states as its parent class" do
288
274
  expect(DerivateNewDsl.aasm.states).to eq(SimpleNewDsl.aasm.states)
@@ -302,7 +288,7 @@ describe "named scopes with the new DSL" do
302
288
  context "Does not already respond_to? the scope name" do
303
289
  it "should add a scope" do
304
290
  expect(SimpleNewDsl).to respond_to(:unknown_scope)
305
- expect(SimpleNewDsl.unknown_scope.is_a?(ActiveRecord::Relation)).to be_true
291
+ expect(SimpleNewDsl.unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
306
292
  end
307
293
  end
308
294
 
@@ -361,7 +347,7 @@ describe 'transitions with persistence' do
361
347
 
362
348
  validator.name = nil
363
349
  expect(validator).not_to be_valid
364
- expect(validator.run!).to be_false
350
+ expect(validator.run!).to be_falsey
365
351
  expect(validator).to be_sleeping
366
352
 
367
353
  validator.reload
@@ -370,7 +356,7 @@ describe 'transitions with persistence' do
370
356
 
371
357
  validator.name = 'another name'
372
358
  expect(validator).to be_valid
373
- expect(validator.run!).to be_true
359
+ expect(validator.run!).to be_truthy
374
360
  expect(validator).to be_running
375
361
 
376
362
  validator.reload
@@ -385,7 +371,7 @@ describe 'transitions with persistence' do
385
371
 
386
372
  persistor.name = nil
387
373
  expect(persistor).not_to be_valid
388
- expect(persistor.run!).to be_true
374
+ expect(persistor.run!).to be_truthy
389
375
  expect(persistor).to be_running
390
376
 
391
377
  persistor = InvalidPersistor.find(persistor.id)
@@ -490,7 +476,7 @@ describe "invalid states with persistence" do
490
476
  it "should not store states" do
491
477
  validator = Validator.create(:name => 'name')
492
478
  validator.status = 'invalid_state'
493
- expect(validator.save).to be_false
479
+ expect(validator.save).to be_falsey
494
480
  expect {validator.save!}.to raise_error(ActiveRecord::RecordInvalid)
495
481
 
496
482
  validator.reload
@@ -500,7 +486,7 @@ describe "invalid states with persistence" do
500
486
  it "should store invalid states if configured" do
501
487
  persistor = InvalidPersistor.create(:name => 'name')
502
488
  persistor.status = 'invalid_state'
503
- expect(persistor.save).to be_true
489
+ expect(persistor.save).to be_truthy
504
490
 
505
491
  persistor.reload
506
492
  expect(persistor.status).to eq('invalid_state')
@@ -0,0 +1,135 @@
1
+ describe 'mongo_mapper' do
2
+ begin
3
+ require 'mongo_mapper'
4
+ require 'logger'
5
+ require 'spec_helper'
6
+
7
+ before(:all) do
8
+ Dir[File.dirname(__FILE__) + "/../../models/mongo_mapper/*.rb"].sort.each { |f| require File.expand_path(f) }
9
+
10
+ config = {
11
+ 'test' => {
12
+ 'database' => "mongo_mapper_#{Process.pid}"
13
+ }
14
+ }
15
+
16
+ MongoMapper.setup(config, 'test') #, :logger => Logger.new(STDERR))
17
+ end
18
+
19
+ after do
20
+ # Clear Out all non-system Mongo collections between tests
21
+ MongoMapper.database.collections.each do |collection|
22
+ collection.drop unless collection.capped? || (collection.name =~ /\Asystem/)
23
+ end
24
+ end
25
+
26
+ describe "named scopes with the old DSL" do
27
+
28
+ context "Does not already respond_to? the scope name" do
29
+ it "should add a scope" do
30
+ expect(SimpleMongoMapper).to respond_to(:unknown_scope)
31
+ expect(SimpleMongoMapper.unknown_scope.class).to eq(MongoMapper::Plugins::Querying::DecoratedPluckyQuery)
32
+ #expect(SimpleMongoMapper.unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
33
+ end
34
+ end
35
+
36
+ context "Already respond_to? the scope name" do
37
+ it "should not add a scope" do
38
+ expect(SimpleMongoMapper).to respond_to(:next)
39
+ expect(SimpleMongoMapper.new.class).to eq(SimpleMongoMapper)
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ describe "named scopes with the new DSL" do
46
+
47
+ context "Does not already respond_to? the scope name" do
48
+ it "should add a scope" do
49
+ expect(SimpleNewDslMongoMapper).to respond_to(:unknown_scope)
50
+ expect(SimpleNewDslMongoMapper.unknown_scope.class).to eq(MongoMapper::Plugins::Querying::DecoratedPluckyQuery)
51
+ end
52
+ end
53
+
54
+ context "Already respond_to? the scope name" do
55
+ it "should not add a scope" do
56
+ expect(SimpleNewDslMongoMapper).to respond_to(:next)
57
+ expect(SimpleNewDslMongoMapper.new.class).to eq(SimpleNewDslMongoMapper)
58
+ end
59
+ end
60
+
61
+ it "does not create scopes if requested" do
62
+ expect(NoScopeMongoMapper).not_to respond_to(:ignored_scope)
63
+ end
64
+
65
+ end
66
+
67
+ describe "#find_in_state" do
68
+
69
+ let!(:model) { SimpleNewDslMongoMapper.create!(:status => :unknown_scope) }
70
+ let!(:model_id) { model._id }
71
+
72
+ it "should respond to method" do
73
+ expect(SimpleNewDslMongoMapper).to respond_to(:find_in_state)
74
+ end
75
+
76
+ it "should find the model when given the correct scope and model id" do
77
+ expect(SimpleNewDslMongoMapper.find_in_state(model_id, 'unknown_scope').class).to eq(SimpleNewDslMongoMapper)
78
+ expect(SimpleNewDslMongoMapper.find_in_state(model_id, 'unknown_scope')).to eq(model)
79
+ end
80
+
81
+ it "should raise DocumentNotFound error when given incorrect scope" do
82
+ expect {SimpleNewDslMongoMapper.find_in_state(model_id, 'next')}.to raise_error MongoMapper::DocumentNotFound
83
+ end
84
+
85
+ it "should raise DocumentNotFound error when given incorrect model id" do
86
+ expect {SimpleNewDslMongoMapper.find_in_state('bad_id', 'unknown_scope')}.to raise_error MongoMapper::DocumentNotFound
87
+ end
88
+
89
+ end
90
+
91
+ describe "#count_in_state" do
92
+
93
+ before do
94
+ 3.times { SimpleNewDslMongoMapper.create!(:status => :unknown_scope) }
95
+ end
96
+
97
+ it "should respond to method" do
98
+ expect(SimpleNewDslMongoMapper).to respond_to(:count_in_state)
99
+ end
100
+
101
+ it "should return n for a scope with n records persisted" do
102
+ expect(SimpleNewDslMongoMapper.count_in_state('unknown_scope').class).to eq(Fixnum)
103
+ expect(SimpleNewDslMongoMapper.count_in_state('unknown_scope')).to eq(3)
104
+ end
105
+
106
+ it "should return zero for a scope without records persisted" do
107
+ expect(SimpleNewDslMongoMapper.count_in_state('next').class).to eq(Fixnum)
108
+ expect(SimpleNewDslMongoMapper.count_in_state('next')).to eq(0)
109
+ end
110
+
111
+ end
112
+
113
+ describe "instance methods" do
114
+
115
+ let(:simple) {SimpleNewDslMongoMapper.new}
116
+
117
+ it "should call aasm_ensure_initial_state on validation before create" do
118
+ expect(SimpleNewDslMongoMapper.aasm.initial_state).to eq(:unknown_scope)
119
+ expect(SimpleNewDslMongoMapper.aasm.attribute_name).to eq(:status)
120
+ expect(simple.status).to eq(nil)
121
+ simple.valid?
122
+ expect(simple.status).to eq('unknown_scope')
123
+ end
124
+
125
+ it "should call aasm_ensure_initial_state before create, even if skipping validations" do
126
+ expect(simple.status).to eq(nil)
127
+ simple.save(:validate => false)
128
+ expect(simple.status).to eq('unknown_scope')
129
+ end
130
+ end
131
+
132
+ rescue LoadError
133
+ puts "Not running MongoMapper specs because mongo_mapper gem is not installed!!!"
134
+ end
135
+ end
@@ -1,6 +1,4 @@
1
- describe 'mongoid', :if => Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version.create('1.9.3') do
2
- # describe 'mongoid' do
3
-
1
+ describe 'mongoid' do
4
2
  begin
5
3
  require 'mongoid'
6
4
  require 'logger'
@@ -135,15 +133,11 @@ describe 'mongoid', :if => Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version
135
133
  describe "instance methods" do
136
134
  let(:simple) {SimpleNewDslMongoid.new}
137
135
 
138
- it "should call aasm_ensure_initial_state on validation before create" do
139
- expect(simple).to receive(:aasm_ensure_initial_state).and_return(true)
140
- simple.valid?
136
+ it "should initialize the aasm state on instantiation" do
137
+ expect(SimpleNewDslMongoid.new.status).to eql 'unknown_scope'
138
+ expect(SimpleNewDslMongoid.new.aasm.current_state).to eql :unknown_scope
141
139
  end
142
140
 
143
- it "should call aasm_ensure_initial_state before create, even if skipping validations" do
144
- expect(simple).to receive(:aasm_ensure_initial_state).and_return(true)
145
- simple.save(:validate => false)
146
- end
147
141
  end
148
142
 
149
143
  rescue LoadError
@@ -6,7 +6,8 @@ describe 'sequel' do
6
6
  require 'spec_helper'
7
7
 
8
8
  before(:all) do
9
- db = Sequel.sqlite
9
+ db = Sequel.connect(SEQUEL_DB)
10
+
10
11
  # if you want to see the statements while running the spec enable the following line
11
12
  # db.loggers << Logger.new($stderr)
12
13
  db.create_table(:models) do
@@ -1,13 +1,13 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe AASM::State do
3
+ describe AASM::Core::State do
4
4
  before(:each) do
5
5
  @name = :astate
6
6
  @options = { :crazy_custom_key => 'key' }
7
7
  end
8
8
 
9
9
  def new_state(options={})
10
- AASM::State.new(@name, Conversation, @options.merge(options))
10
+ AASM::Core::State.new(@name, Conversation, @options.merge(options))
11
11
  end
12
12
 
13
13
  it 'should set the name' do
@@ -19,7 +19,7 @@ describe 'subclassing' do
19
19
  end
20
20
 
21
21
  it 'should know how to respond to `may_add_details?`' do
22
- expect(son.may_add_details?).to be_true
22
+ expect(son.may_add_details?).to be_truthy
23
23
  end
24
24
 
25
25
  it 'should not break if I call Son#update_state' do