aasm 4.11.1 → 5.2.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.
- checksums.yaml +5 -5
- data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.travis.yml +56 -23
- data/Appraisals +67 -0
- data/CHANGELOG.md +112 -0
- data/CONTRIBUTING.md +24 -0
- data/Dockerfile +44 -0
- data/Gemfile +3 -21
- data/Gemfile.lock_old +151 -0
- data/LICENSE +1 -1
- data/README.md +540 -139
- data/Rakefile +6 -1
- data/TESTING.md +25 -0
- data/aasm.gemspec +5 -0
- data/docker-compose.yml +40 -0
- data/gemfiles/norails.gemfile +10 -0
- data/gemfiles/rails_4.2.gemfile +13 -11
- data/gemfiles/rails_4.2_mongoid_5.gemfile +8 -11
- data/gemfiles/rails_4.2_nobrainer.gemfile +9 -0
- data/gemfiles/rails_5.0.gemfile +11 -18
- data/gemfiles/rails_5.0_nobrainer.gemfile +9 -0
- data/gemfiles/rails_5.1.gemfile +14 -0
- data/gemfiles/rails_5.2.gemfile +14 -0
- data/lib/aasm/aasm.rb +40 -29
- data/lib/aasm/base.rb +61 -11
- data/lib/aasm/configuration.rb +10 -0
- data/lib/aasm/core/event.rb +45 -37
- data/lib/aasm/core/invoker.rb +129 -0
- data/lib/aasm/core/invokers/base_invoker.rb +75 -0
- data/lib/aasm/core/invokers/class_invoker.rb +52 -0
- data/lib/aasm/core/invokers/literal_invoker.rb +47 -0
- data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
- data/lib/aasm/core/state.rb +22 -13
- data/lib/aasm/core/transition.rb +17 -69
- data/lib/aasm/dsl_helper.rb +24 -22
- data/lib/aasm/errors.rb +4 -6
- data/lib/aasm/instance_base.rb +22 -4
- data/lib/aasm/localizer.rb +13 -3
- data/lib/aasm/minitest/allow_event.rb +13 -0
- data/lib/aasm/minitest/allow_transition_to.rb +13 -0
- data/lib/aasm/minitest/have_state.rb +13 -0
- data/lib/aasm/minitest/transition_from.rb +21 -0
- data/lib/aasm/minitest.rb +5 -0
- data/lib/aasm/minitest_spec.rb +15 -0
- data/lib/aasm/persistence/active_record_persistence.rb +49 -105
- data/lib/aasm/persistence/base.rb +20 -5
- data/lib/aasm/persistence/core_data_query_persistence.rb +2 -1
- data/lib/aasm/persistence/dynamoid_persistence.rb +1 -1
- data/lib/aasm/persistence/mongoid_persistence.rb +26 -32
- data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
- data/lib/aasm/persistence/orm.rb +154 -0
- data/lib/aasm/persistence/plain_persistence.rb +2 -1
- data/lib/aasm/persistence/redis_persistence.rb +16 -11
- data/lib/aasm/persistence/sequel_persistence.rb +36 -64
- data/lib/aasm/persistence.rb +3 -3
- data/lib/aasm/rspec/allow_event.rb +5 -1
- data/lib/aasm/rspec/allow_transition_to.rb +5 -1
- data/lib/aasm/rspec/transition_from.rb +5 -1
- data/lib/aasm/state_machine.rb +4 -2
- data/lib/aasm/state_machine_store.rb +5 -2
- data/lib/aasm/version.rb +1 -1
- data/lib/aasm.rb +5 -2
- data/lib/generators/aasm/orm_helpers.rb +6 -0
- data/lib/generators/active_record/aasm_generator.rb +3 -1
- data/lib/generators/active_record/templates/migration.rb +1 -1
- data/lib/generators/active_record/templates/migration_existing.rb +1 -1
- data/lib/generators/nobrainer/aasm_generator.rb +28 -0
- data/lib/motion-aasm.rb +3 -1
- data/spec/database.rb +20 -7
- data/spec/en.yml +0 -3
- data/spec/generators/active_record_generator_spec.rb +49 -40
- data/spec/generators/mongoid_generator_spec.rb +4 -6
- data/spec/generators/no_brainer_generator_spec.rb +29 -0
- data/spec/{en_deprecated_style.yml → localizer_test_model_deprecated_style.yml} +6 -3
- data/spec/localizer_test_model_new_style.yml +11 -0
- data/spec/models/active_record/active_record_callback.rb +93 -0
- data/spec/models/active_record/complex_active_record_example.rb +5 -1
- data/spec/models/active_record/instance_level_skip_validation_example.rb +19 -0
- data/spec/models/{invalid_persistor.rb → active_record/invalid_persistor.rb} +0 -2
- data/spec/models/active_record/localizer_test_model.rb +11 -3
- data/spec/models/active_record/namespaced.rb +16 -0
- data/spec/models/active_record/person.rb +23 -0
- data/spec/models/{silent_persistor.rb → active_record/silent_persistor.rb} +0 -2
- data/spec/models/active_record/simple_new_dsl.rb +15 -0
- data/spec/models/active_record/timestamp_example.rb +16 -0
- data/spec/models/{transactor.rb → active_record/transactor.rb} +25 -2
- data/spec/models/{validator.rb → active_record/validator.rb} +0 -2
- data/spec/models/active_record/work.rb +3 -0
- data/spec/models/{worker.rb → active_record/worker.rb} +0 -0
- data/spec/models/callbacks/basic.rb +5 -2
- data/spec/models/callbacks/with_state_arg.rb +5 -1
- data/spec/models/callbacks/with_state_arg_multiple.rb +4 -1
- data/spec/models/default_state.rb +1 -1
- data/spec/models/guard_arguments_check.rb +17 -0
- data/spec/models/guard_with_params.rb +1 -1
- data/spec/models/guardian_without_from_specified.rb +18 -0
- data/spec/models/mongoid/invalid_persistor_mongoid.rb +39 -0
- data/spec/models/mongoid/silent_persistor_mongoid.rb +39 -0
- data/spec/models/mongoid/timestamp_example_mongoid.rb +20 -0
- data/spec/models/mongoid/validator_mongoid.rb +100 -0
- data/spec/models/multiple_transitions_that_differ_only_by_guard.rb +31 -0
- data/spec/models/namespaced_multiple_example.rb +14 -0
- data/spec/models/nobrainer/complex_no_brainer_example.rb +36 -0
- data/spec/models/nobrainer/invalid_persistor_no_brainer.rb +39 -0
- data/spec/models/nobrainer/no_scope_no_brainer.rb +21 -0
- data/spec/models/nobrainer/nobrainer_relationships.rb +25 -0
- data/spec/models/nobrainer/silent_persistor_no_brainer.rb +39 -0
- data/spec/models/nobrainer/simple_new_dsl_nobrainer.rb +25 -0
- data/spec/models/{mongo_mapper/simple_mongo_mapper.rb → nobrainer/simple_no_brainer.rb} +8 -8
- data/spec/models/nobrainer/validator_no_brainer.rb +98 -0
- data/spec/models/parametrised_event.rb +7 -0
- data/spec/models/{mongo_mapper/complex_mongo_mapper_example.rb → redis/complex_redis_example.rb} +8 -5
- data/spec/models/redis/redis_multiple.rb +20 -0
- data/spec/models/redis/redis_simple.rb +20 -0
- data/spec/models/sequel/complex_sequel_example.rb +4 -3
- data/spec/models/sequel/invalid_persistor.rb +52 -0
- data/spec/models/sequel/sequel_multiple.rb +13 -13
- data/spec/models/sequel/sequel_simple.rb +13 -12
- data/spec/models/sequel/silent_persistor.rb +50 -0
- data/spec/models/sequel/transactor.rb +112 -0
- data/spec/models/sequel/validator.rb +93 -0
- data/spec/models/sequel/worker.rb +12 -0
- data/spec/models/simple_example.rb +8 -0
- data/spec/models/simple_example_with_guard_args.rb +17 -0
- data/spec/models/simple_multiple_example.rb +12 -0
- data/spec/models/sub_class.rb +34 -0
- data/spec/models/timestamps_example.rb +19 -0
- data/spec/models/timestamps_with_named_machine_example.rb +13 -0
- data/spec/spec_helper.rb +15 -33
- data/spec/spec_helpers/active_record.rb +8 -0
- data/spec/spec_helpers/dynamoid.rb +35 -0
- data/spec/spec_helpers/mongoid.rb +26 -0
- data/spec/spec_helpers/nobrainer.rb +15 -0
- data/spec/spec_helpers/redis.rb +18 -0
- data/spec/spec_helpers/remove_warnings.rb +1 -0
- data/spec/spec_helpers/sequel.rb +7 -0
- data/spec/unit/abstract_class_spec.rb +27 -0
- data/spec/unit/api_spec.rb +79 -72
- data/spec/unit/callback_multiple_spec.rb +7 -3
- data/spec/unit/callbacks_spec.rb +37 -2
- data/spec/unit/complex_example_spec.rb +12 -3
- data/spec/unit/complex_multiple_example_spec.rb +20 -4
- data/spec/unit/event_multiple_spec.rb +1 -1
- data/spec/unit/event_spec.rb +29 -4
- data/spec/unit/exception_spec.rb +1 -1
- data/spec/unit/guard_arguments_check_spec.rb +9 -0
- data/spec/unit/guard_spec.rb +17 -0
- data/spec/unit/guard_with_params_spec.rb +4 -0
- data/spec/unit/guard_without_from_specified_spec.rb +10 -0
- data/spec/unit/inspection_multiple_spec.rb +9 -5
- data/spec/unit/inspection_spec.rb +7 -3
- data/spec/unit/invoker_spec.rb +189 -0
- data/spec/unit/invokers/base_invoker_spec.rb +72 -0
- data/spec/unit/invokers/class_invoker_spec.rb +95 -0
- data/spec/unit/invokers/literal_invoker_spec.rb +86 -0
- data/spec/unit/invokers/proc_invoker_spec.rb +86 -0
- data/spec/unit/localizer_spec.rb +85 -52
- data/spec/unit/multiple_transitions_that_differ_only_by_guard_spec.rb +14 -0
- data/spec/unit/namespaced_multiple_example_spec.rb +22 -0
- data/spec/unit/override_warning_spec.rb +8 -0
- data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +468 -447
- data/spec/unit/persistence/active_record_persistence_spec.rb +639 -486
- data/spec/unit/persistence/dynamoid_persistence_multiple_spec.rb +4 -9
- data/spec/unit/persistence/dynamoid_persistence_spec.rb +4 -9
- data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +83 -13
- data/spec/unit/persistence/mongoid_persistence_spec.rb +97 -13
- data/spec/unit/persistence/no_brainer_persistence_multiple_spec.rb +198 -0
- data/spec/unit/persistence/no_brainer_persistence_spec.rb +158 -0
- data/spec/unit/persistence/redis_persistence_multiple_spec.rb +88 -0
- data/spec/unit/persistence/redis_persistence_spec.rb +8 -32
- data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +6 -11
- data/spec/unit/persistence/sequel_persistence_spec.rb +278 -10
- data/spec/unit/rspec_matcher_spec.rb +9 -0
- data/spec/unit/simple_example_spec.rb +15 -0
- data/spec/unit/simple_multiple_example_spec.rb +28 -0
- data/spec/unit/state_spec.rb +23 -7
- data/spec/unit/subclassing_multiple_spec.rb +37 -2
- data/spec/unit/subclassing_spec.rb +17 -2
- data/spec/unit/timestamps_spec.rb +32 -0
- data/spec/unit/transition_spec.rb +1 -1
- data/test/minitest_helper.rb +57 -0
- data/test/unit/minitest_matcher_test.rb +80 -0
- metadata +213 -37
- data/callbacks.txt +0 -51
- data/gemfiles/rails_3.2_stable.gemfile +0 -15
- data/gemfiles/rails_4.0.gemfile +0 -16
- data/gemfiles/rails_4.0_mongo_mapper.gemfile +0 -16
- data/gemfiles/rails_4.2_mongo_mapper.gemfile +0 -17
- data/lib/aasm/persistence/mongo_mapper_persistence.rb +0 -163
- data/spec/models/mongo_mapper/no_scope_mongo_mapper.rb +0 -21
- data/spec/models/mongo_mapper/simple_new_dsl_mongo_mapper.rb +0 -25
- data/spec/unit/persistence/mongo_mapper_persistence_multiple_spec.rb +0 -149
- data/spec/unit/persistence/mongo_mapper_persistence_spec.rb +0 -96
|
@@ -1,699 +1,852 @@
|
|
|
1
|
-
require 'active_record'
|
|
2
1
|
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
2
|
|
|
9
|
-
|
|
10
|
-
# require 'logger'
|
|
11
|
-
# ActiveRecord::Base.logger = Logger.new(STDERR)
|
|
3
|
+
if defined?(ActiveRecord)
|
|
12
4
|
|
|
13
|
-
|
|
14
|
-
|
|
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)
|
|
5
|
+
Dir[File.dirname(__FILE__) + "/../../models/active_record/*.rb"].sort.each do |f|
|
|
6
|
+
require File.expand_path(f)
|
|
20
7
|
end
|
|
21
8
|
|
|
22
|
-
|
|
23
|
-
subject { lambda{ gate.send(:aasm_column_looks_like_enum) } }
|
|
9
|
+
load_schema
|
|
24
10
|
|
|
25
|
-
|
|
26
|
-
|
|
11
|
+
# if you want to see the statements while running the spec enable the following line
|
|
12
|
+
# require 'logger'
|
|
13
|
+
# ActiveRecord::Base.logger = Logger.new(STDERR)
|
|
27
14
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
allow(gate.class).to receive(:columns_hash).and_return(columns_hash)
|
|
31
|
-
end
|
|
15
|
+
describe "instance methods" do
|
|
16
|
+
let(:gate) {Gate.new}
|
|
32
17
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
expect(subject.call).to be_truthy
|
|
38
|
-
end
|
|
18
|
+
it "should respond to aasm persistence methods" do
|
|
19
|
+
expect(gate).to respond_to(:aasm_read_state)
|
|
20
|
+
expect(gate).to respond_to(:aasm_write_state)
|
|
21
|
+
expect(gate).to respond_to(:aasm_write_state_without_persistence)
|
|
39
22
|
end
|
|
40
23
|
|
|
41
|
-
|
|
42
|
-
|
|
24
|
+
describe "aasm_column_looks_like_enum" do
|
|
25
|
+
subject { lambda{ gate.send(:aasm_column_looks_like_enum) } }
|
|
43
26
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
27
|
+
let(:column_name) { "value" }
|
|
28
|
+
let(:columns_hash) { Hash[column_name, column] }
|
|
49
29
|
|
|
50
|
-
|
|
51
|
-
|
|
30
|
+
before :each do
|
|
31
|
+
allow(gate.class.aasm).to receive(:attribute_name).and_return(column_name.to_sym)
|
|
32
|
+
allow(gate.class).to receive(:columns_hash).and_return(columns_hash)
|
|
33
|
+
end
|
|
52
34
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
35
|
+
context "when AASM column has integer type" do
|
|
36
|
+
let(:column) { double(Object, type: :integer) }
|
|
56
37
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
38
|
+
it "returns true" do
|
|
39
|
+
expect(subject.call).to be_truthy
|
|
40
|
+
end
|
|
41
|
+
end
|
|
61
42
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
let(:with_enum) { WithEnum.new }
|
|
43
|
+
context "when AASM column has string type" do
|
|
44
|
+
let(:column) { double(Object, type: :string) }
|
|
65
45
|
|
|
66
|
-
|
|
67
|
-
|
|
46
|
+
it "returns false" do
|
|
47
|
+
expect(subject.call).to be_falsey
|
|
48
|
+
end
|
|
68
49
|
end
|
|
69
50
|
end
|
|
70
51
|
|
|
71
|
-
|
|
72
|
-
|
|
52
|
+
describe "aasm_guess_enum_method" do
|
|
53
|
+
subject { lambda{ gate.send(:aasm_guess_enum_method) } }
|
|
54
|
+
|
|
73
55
|
before :each do
|
|
74
|
-
allow(
|
|
56
|
+
allow(gate.class.aasm).to receive(:attribute_name).and_return(:value)
|
|
75
57
|
end
|
|
76
58
|
|
|
77
|
-
it "
|
|
78
|
-
expect(
|
|
59
|
+
it "pluralizes AASM column name" do
|
|
60
|
+
expect(subject.call).to eq :values
|
|
79
61
|
end
|
|
80
62
|
end
|
|
81
63
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
it "returns nil" do
|
|
86
|
-
expect(with_false_enum.send(:aasm_enum)).to be_nil
|
|
87
|
-
end
|
|
88
|
-
end
|
|
64
|
+
describe "aasm_enum" do
|
|
65
|
+
context "when AASM enum setting contains an explicit enum method name" do
|
|
66
|
+
let(:with_enum) { WithEnum.new }
|
|
89
67
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
68
|
+
it "returns whatever value was set in AASM config" do
|
|
69
|
+
expect(with_enum.send(:aasm_enum)).to eq :test
|
|
70
|
+
end
|
|
93
71
|
end
|
|
94
72
|
|
|
95
|
-
context "when AASM
|
|
73
|
+
context "when AASM enum setting is simply set to true" do
|
|
74
|
+
let(:with_true_enum) { WithTrueEnum.new }
|
|
96
75
|
before :each do
|
|
97
|
-
allow(
|
|
76
|
+
allow(WithTrueEnum.aasm).to receive(:attribute_name).and_return(:value)
|
|
98
77
|
end
|
|
99
78
|
|
|
100
79
|
it "infers enum method name from pluralized column name" do
|
|
101
|
-
expect(
|
|
80
|
+
expect(with_true_enum.send(:aasm_enum)).to eq :values
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context "when AASM enum setting is explicitly disabled" do
|
|
85
|
+
let(:with_false_enum) { WithFalseEnum.new }
|
|
86
|
+
|
|
87
|
+
it "returns nil" do
|
|
88
|
+
expect(with_false_enum.send(:aasm_enum)).to be_nil
|
|
102
89
|
end
|
|
103
90
|
end
|
|
104
91
|
|
|
105
|
-
context "when AASM
|
|
92
|
+
context "when AASM enum setting is not enabled" do
|
|
106
93
|
before :each do
|
|
107
|
-
allow(
|
|
108
|
-
.and_return(false)
|
|
94
|
+
allow(Gate.aasm).to receive(:attribute_name).and_return(:value)
|
|
109
95
|
end
|
|
110
96
|
|
|
111
|
-
|
|
112
|
-
|
|
97
|
+
context "when AASM column looks like enum" do
|
|
98
|
+
before :each do
|
|
99
|
+
allow(gate).to receive(:aasm_column_looks_like_enum).and_return(true)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "infers enum method name from pluralized column name" do
|
|
103
|
+
expect(gate.send(:aasm_enum)).to eq :values
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
context "when AASM column doesn't look like enum'" do
|
|
108
|
+
before :each do
|
|
109
|
+
allow(gate).to receive(:aasm_column_looks_like_enum)
|
|
110
|
+
.and_return(false)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "returns nil, as we're not using enum" do
|
|
114
|
+
expect(gate.send(:aasm_enum)).to be_nil
|
|
115
|
+
end
|
|
113
116
|
end
|
|
114
117
|
end
|
|
115
|
-
end
|
|
116
118
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
if ActiveRecord::VERSION::MAJOR >= 4 && ActiveRecord::VERSION::MINOR >= 1 # won't work with Rails <= 4.1
|
|
120
|
+
# Enum are introduced from Rails 4.1, therefore enum syntax will not work on Rails <= 4.1
|
|
121
|
+
context "when AASM enum setting is not enabled and aasm column not present" do
|
|
120
122
|
|
|
121
|
-
|
|
123
|
+
let(:with_enum_without_column) {WithEnumWithoutColumn.new}
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
it "should raise NoMethodError for transitions" do
|
|
126
|
+
expect{with_enum_without_column.send(:view)}.to raise_error(NoMethodError, /undefined method .status./)
|
|
127
|
+
end
|
|
125
128
|
end
|
|
129
|
+
|
|
126
130
|
end
|
|
127
131
|
|
|
128
132
|
end
|
|
129
133
|
|
|
130
|
-
|
|
134
|
+
context "when AASM is configured to use enum" do
|
|
135
|
+
let(:state_sym) { :running }
|
|
136
|
+
let(:state_code) { 2 }
|
|
137
|
+
let(:enum_name) { :states }
|
|
138
|
+
let(:enum) { Hash[state_sym, state_code] }
|
|
131
139
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
let(:enum) { Hash[state_sym, state_code] }
|
|
140
|
+
before :each do
|
|
141
|
+
allow(gate).to receive(:aasm_enum).and_return(enum_name)
|
|
142
|
+
allow(gate).to receive(:aasm_write_state_attribute)
|
|
143
|
+
allow(gate).to receive(:write_attribute)
|
|
137
144
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
allow(gate).to receive(:aasm_write_attribute)
|
|
141
|
-
allow(gate).to receive(:write_attribute)
|
|
145
|
+
allow(Gate).to receive(enum_name).and_return(enum)
|
|
146
|
+
end
|
|
142
147
|
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
describe "aasm_write_state" do
|
|
149
|
+
context "when AASM is configured to skip validations on save" do
|
|
150
|
+
before :each do
|
|
151
|
+
allow(gate).to receive(:aasm_skipping_validations).and_return(true)
|
|
152
|
+
end
|
|
145
153
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
it "passes state code instead of state symbol to update_all" do
|
|
155
|
+
# stub_chain does not allow us to give expectations on call
|
|
156
|
+
# parameters in the middle of the chain, so we need to use
|
|
157
|
+
# intermediate object instead.
|
|
158
|
+
obj = double(Object, update_all: 1)
|
|
159
|
+
allow(Gate).to receive_message_chain(:unscoped, :where).and_return(obj)
|
|
160
|
+
|
|
161
|
+
gate.aasm_write_state state_sym
|
|
162
|
+
|
|
163
|
+
expect(obj).to have_received(:update_all)
|
|
164
|
+
.with(Hash[gate.class.aasm.attribute_name, state_code])
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "searches model outside of default_scope when update_all" do
|
|
168
|
+
# stub_chain does not allow us to give expectations on call
|
|
169
|
+
# parameters in the middle of the chain, so we need to use
|
|
170
|
+
# intermediate object instead.
|
|
171
|
+
unscoped = double(Object, update_all: 1)
|
|
172
|
+
scoped = double(Object, update_all: 1)
|
|
173
|
+
|
|
174
|
+
allow(Gate).to receive(:unscoped).and_return(unscoped)
|
|
175
|
+
allow(Gate).to receive(:where).and_return(scoped)
|
|
176
|
+
allow(unscoped).to receive(:where).and_return(unscoped)
|
|
177
|
+
|
|
178
|
+
gate.aasm_write_state state_sym
|
|
179
|
+
|
|
180
|
+
expect(unscoped).to have_received(:update_all)
|
|
181
|
+
.with(Hash[gate.class.aasm.attribute_name, state_code])
|
|
182
|
+
expect(scoped).to_not have_received(:update_all)
|
|
183
|
+
.with(Hash[gate.class.aasm.attribute_name, state_code])
|
|
184
|
+
end
|
|
150
185
|
end
|
|
151
186
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
obj = double(Object, update_all: 1)
|
|
157
|
-
allow(Gate).to receive(:where).and_return(obj)
|
|
187
|
+
context "when AASM is not skipping validations" do
|
|
188
|
+
it "delegates state update to the helper method" do
|
|
189
|
+
# Let's pretend that validation is passed
|
|
190
|
+
allow(gate).to receive(:save).and_return(true)
|
|
158
191
|
|
|
159
|
-
|
|
192
|
+
gate.aasm_write_state state_sym
|
|
160
193
|
|
|
161
|
-
|
|
162
|
-
|
|
194
|
+
expect(gate).to have_received(:aasm_write_state_attribute).with(state_sym, :default)
|
|
195
|
+
expect(gate).to_not have_received :write_attribute
|
|
196
|
+
end
|
|
163
197
|
end
|
|
164
198
|
end
|
|
165
199
|
|
|
166
|
-
|
|
200
|
+
describe "aasm_write_state_without_persistence" do
|
|
167
201
|
it "delegates state update to the helper method" do
|
|
168
|
-
|
|
169
|
-
allow(gate).to receive(:save).and_return(true)
|
|
170
|
-
|
|
171
|
-
gate.aasm_write_state state_sym
|
|
202
|
+
gate.aasm_write_state_without_persistence state_sym
|
|
172
203
|
|
|
173
|
-
expect(gate).to have_received(:
|
|
204
|
+
expect(gate).to have_received(:aasm_write_state_attribute).with(state_sym, :default)
|
|
174
205
|
expect(gate).to_not have_received :write_attribute
|
|
175
206
|
end
|
|
176
207
|
end
|
|
208
|
+
|
|
209
|
+
describe "aasm_raw_attribute_value" do
|
|
210
|
+
it "converts state symbol to state code" do
|
|
211
|
+
expect(gate.send(:aasm_raw_attribute_value, state_sym))
|
|
212
|
+
.to eq state_code
|
|
213
|
+
end
|
|
214
|
+
end
|
|
177
215
|
end
|
|
178
216
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
gate.aasm_write_state_without_persistence state_sym
|
|
217
|
+
context "when AASM is configured to use string field" do
|
|
218
|
+
let(:state_sym) { :running }
|
|
182
219
|
|
|
183
|
-
|
|
184
|
-
|
|
220
|
+
before :each do
|
|
221
|
+
allow(gate).to receive(:aasm_enum).and_return(nil)
|
|
185
222
|
end
|
|
186
|
-
end
|
|
187
223
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
224
|
+
describe "aasm_raw_attribute_value" do
|
|
225
|
+
it "converts state symbol to string" do
|
|
226
|
+
expect(gate.send(:aasm_raw_attribute_value, state_sym))
|
|
227
|
+
.to eq state_sym.to_s
|
|
228
|
+
end
|
|
192
229
|
end
|
|
193
230
|
end
|
|
194
|
-
end
|
|
195
231
|
|
|
196
|
-
|
|
197
|
-
|
|
232
|
+
describe "aasm_write_attribute helper method" do
|
|
233
|
+
let(:sym) { :sym }
|
|
234
|
+
let(:value) { 42 }
|
|
198
235
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
236
|
+
before :each do
|
|
237
|
+
allow(gate).to receive(:write_attribute)
|
|
238
|
+
allow(gate).to receive(:aasm_raw_attribute_value).and_return(value)
|
|
202
239
|
|
|
203
|
-
|
|
204
|
-
it "converts state symbol to string" do
|
|
205
|
-
expect(gate.send(:aasm_raw_attribute_value, state_sym))
|
|
206
|
-
.to eq state_sym.to_s
|
|
240
|
+
gate.send(:aasm_write_state_attribute, sym)
|
|
207
241
|
end
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
242
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
243
|
+
it "generates attribute value using a helper method" do
|
|
244
|
+
expect(gate).to have_received(:aasm_raw_attribute_value).with(sym, :default)
|
|
245
|
+
end
|
|
214
246
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
247
|
+
it "writes attribute to the model" do
|
|
248
|
+
expect(gate).to have_received(:write_attribute).with(:aasm_state, value)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
218
251
|
|
|
219
|
-
|
|
252
|
+
it "should return the initial state when new and the aasm field is nil" do
|
|
253
|
+
expect(gate.aasm.current_state).to eq(:opened)
|
|
220
254
|
end
|
|
221
255
|
|
|
222
|
-
it "
|
|
223
|
-
|
|
256
|
+
it "should return the aasm column when new and the aasm field is not nil" do
|
|
257
|
+
gate.aasm_state = "closed"
|
|
258
|
+
expect(gate.aasm.current_state).to eq(:closed)
|
|
224
259
|
end
|
|
225
260
|
|
|
226
|
-
it "
|
|
227
|
-
|
|
261
|
+
it "should return the aasm column when not new and the aasm.attribute_name is not nil" do
|
|
262
|
+
allow(gate).to receive(:new_record?).and_return(false)
|
|
263
|
+
gate.aasm_state = "state"
|
|
264
|
+
expect(gate.aasm.current_state).to eq(:state)
|
|
228
265
|
end
|
|
229
|
-
end
|
|
230
266
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
267
|
+
it "should allow a nil state" do
|
|
268
|
+
allow(gate).to receive(:new_record?).and_return(false)
|
|
269
|
+
gate.aasm_state = nil
|
|
270
|
+
expect(gate.aasm.current_state).to be_nil
|
|
271
|
+
end
|
|
234
272
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
273
|
+
context 'on initialization' do
|
|
274
|
+
it "should initialize the aasm state" do
|
|
275
|
+
expect(Gate.new.aasm_state).to eql 'opened'
|
|
276
|
+
expect(Gate.new.aasm.current_state).to eql :opened
|
|
277
|
+
end
|
|
239
278
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
end
|
|
279
|
+
it "should not initialize the aasm state if it has not been loaded" do
|
|
280
|
+
# we have to create a gate in the database, for which we only want to
|
|
281
|
+
# load the id, and not the state
|
|
282
|
+
gate = Gate.create!
|
|
245
283
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
expect(gate.aasm.current_state).to be_nil
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
context 'on initialization' do
|
|
253
|
-
it "should initialize the aasm state" do
|
|
254
|
-
expect(Gate.new.aasm_state).to eql 'opened'
|
|
255
|
-
expect(Gate.new.aasm.current_state).to eql :opened
|
|
284
|
+
# then we just load the gate ids
|
|
285
|
+
Gate.select(:id).where(id: gate.id).first
|
|
286
|
+
end
|
|
256
287
|
end
|
|
257
288
|
|
|
258
|
-
|
|
259
|
-
# we have to create a gate in the database, for which we only want to
|
|
260
|
-
# load the id, and not the state
|
|
261
|
-
gate = Gate.create!
|
|
289
|
+
end
|
|
262
290
|
|
|
263
|
-
|
|
264
|
-
|
|
291
|
+
if ActiveRecord::VERSION::MAJOR < 4 && ActiveRecord::VERSION::MINOR < 2 # won't work with Rails >= 4.2
|
|
292
|
+
describe "direct state column access" do
|
|
293
|
+
it "accepts false states" do
|
|
294
|
+
f = FalseState.create!
|
|
295
|
+
expect(f.aasm_state).to eql false
|
|
296
|
+
expect {
|
|
297
|
+
f.aasm.events.map(&:name)
|
|
298
|
+
}.to_not raise_error
|
|
265
299
|
end
|
|
266
300
|
end
|
|
301
|
+
end
|
|
267
302
|
|
|
268
|
-
|
|
303
|
+
describe 'subclasses' do
|
|
304
|
+
it "should have the same states as its parent class" do
|
|
305
|
+
expect(DerivateNewDsl.aasm.states).to eq(SimpleNewDsl.aasm.states)
|
|
306
|
+
end
|
|
269
307
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
f = FalseState.create!
|
|
274
|
-
expect(f.aasm_state).to eql false
|
|
275
|
-
expect {
|
|
276
|
-
f.aasm.events.map(&:name)
|
|
277
|
-
}.to_not raise_error
|
|
278
|
-
end
|
|
279
|
-
end
|
|
280
|
-
end
|
|
308
|
+
it "should have the same events as its parent class" do
|
|
309
|
+
expect(DerivateNewDsl.aasm.events).to eq(SimpleNewDsl.aasm.events)
|
|
310
|
+
end
|
|
281
311
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
312
|
+
it "should have the same column as its parent even for the new dsl" do
|
|
313
|
+
expect(SimpleNewDsl.aasm.attribute_name).to eq(:status)
|
|
314
|
+
expect(DerivateNewDsl.aasm.attribute_name).to eq(:status)
|
|
315
|
+
end
|
|
285
316
|
end
|
|
286
317
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
318
|
+
describe "named scopes with the new DSL" do
|
|
319
|
+
context "Does not already respond_to? the scope name" do
|
|
320
|
+
it "should add a scope for each state" do
|
|
321
|
+
expect(SimpleNewDsl).to respond_to(:unknown_scope)
|
|
322
|
+
expect(SimpleNewDsl).to respond_to(:another_unknown_scope)
|
|
290
323
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
324
|
+
expect(SimpleNewDsl.unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
|
|
325
|
+
expect(SimpleNewDsl.another_unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
context "Already respond_to? the scope name" do
|
|
330
|
+
it "should not add a scope" do
|
|
331
|
+
expect(SimpleNewDsl).to respond_to(:new)
|
|
332
|
+
expect(SimpleNewDsl.new.class).to eq(SimpleNewDsl)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
296
335
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
336
|
+
# Scopes on abstract classes didn't work until Rails 5.
|
|
337
|
+
#
|
|
338
|
+
# Reference:
|
|
339
|
+
# https://github.com/rails/rails/issues/10658
|
|
340
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
|
341
|
+
context "For a descendant of an abstract model" do
|
|
342
|
+
it "should add the scope without the table_name" do
|
|
343
|
+
expect(ImplementedAbstractClassDsl).to respond_to(:unknown_scope)
|
|
344
|
+
expect(ImplementedAbstractClassDsl).to respond_to(:another_unknown_scope)
|
|
302
345
|
|
|
303
|
-
|
|
304
|
-
|
|
346
|
+
expect(ImplementedAbstractClassDsl.unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
|
|
347
|
+
expect(ImplementedAbstractClassDsl.another_unknown_scope.is_a?(ActiveRecord::Relation)).to be_truthy
|
|
348
|
+
end
|
|
349
|
+
end
|
|
305
350
|
end
|
|
306
|
-
end
|
|
307
351
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
expect(SimpleNewDsl).to respond_to(:new)
|
|
311
|
-
expect(SimpleNewDsl.new.class).to eq(SimpleNewDsl)
|
|
352
|
+
it "does not create scopes if requested" do
|
|
353
|
+
expect(NoScope).not_to respond_to(:pending)
|
|
312
354
|
end
|
|
313
|
-
end
|
|
314
355
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
356
|
+
context "result of scope" do
|
|
357
|
+
let!(:dsl1) { SimpleNewDsl.create!(status: :new) }
|
|
358
|
+
let!(:dsl2) { SimpleNewDsl.create!(status: :unknown_scope) }
|
|
318
359
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
360
|
+
after do
|
|
361
|
+
SimpleNewDsl.destroy_all
|
|
362
|
+
end
|
|
322
363
|
|
|
323
|
-
|
|
324
|
-
|
|
364
|
+
it "created scope works as where(name: :scope_name)" do
|
|
365
|
+
expect(SimpleNewDsl.unknown_scope).to contain_exactly(dsl2)
|
|
366
|
+
end
|
|
325
367
|
end
|
|
368
|
+
end # scopes
|
|
326
369
|
|
|
327
|
-
|
|
328
|
-
|
|
370
|
+
describe "direct assignment" do
|
|
371
|
+
it "is allowed by default" do
|
|
372
|
+
obj = NoScope.create
|
|
373
|
+
expect(obj.aasm_state.to_sym).to eql :pending
|
|
374
|
+
|
|
375
|
+
obj.aasm_state = :running
|
|
376
|
+
expect(obj.aasm_state.to_sym).to eql :running
|
|
329
377
|
end
|
|
330
|
-
end
|
|
331
|
-
end # scopes
|
|
332
378
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
expect(obj.aasm_state.to_sym).to eql :pending
|
|
379
|
+
it "is forbidden if configured" do
|
|
380
|
+
obj = NoDirectAssignment.create
|
|
381
|
+
expect(obj.aasm_state.to_sym).to eql :pending
|
|
337
382
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
383
|
+
expect {obj.aasm_state = :running}.to raise_error(AASM::NoDirectAssignmentError)
|
|
384
|
+
expect(obj.aasm_state.to_sym).to eql :pending
|
|
385
|
+
end
|
|
341
386
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
387
|
+
it 'can be turned off and on again' do
|
|
388
|
+
obj = NoDirectAssignment.create
|
|
389
|
+
expect(obj.aasm_state.to_sym).to eql :pending
|
|
345
390
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
end
|
|
391
|
+
expect {obj.aasm_state = :running}.to raise_error(AASM::NoDirectAssignmentError)
|
|
392
|
+
expect(obj.aasm_state.to_sym).to eql :pending
|
|
349
393
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
394
|
+
# allow it temporarily
|
|
395
|
+
NoDirectAssignment.aasm.state_machine.config.no_direct_assignment = false
|
|
396
|
+
obj.aasm_state = :running
|
|
397
|
+
expect(obj.aasm_state.to_sym).to eql :running
|
|
353
398
|
|
|
354
|
-
|
|
355
|
-
|
|
399
|
+
# and forbid it again
|
|
400
|
+
NoDirectAssignment.aasm.state_machine.config.no_direct_assignment = true
|
|
401
|
+
expect {obj.aasm_state = :pending}.to raise_error(AASM::NoDirectAssignmentError)
|
|
402
|
+
expect(obj.aasm_state.to_sym).to eql :running
|
|
403
|
+
end
|
|
404
|
+
end # direct assignment
|
|
356
405
|
|
|
357
|
-
|
|
358
|
-
NoDirectAssignment.aasm.state_machine.config.no_direct_assignment = false
|
|
359
|
-
obj.aasm_state = :pending
|
|
360
|
-
expect(obj.aasm_state.to_sym).to eql :pending
|
|
406
|
+
describe 'initial states' do
|
|
361
407
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
408
|
+
it 'should support conditions' do
|
|
409
|
+
expect(Thief.new(:skilled => true).aasm.current_state).to eq(:rich)
|
|
410
|
+
expect(Thief.new(:skilled => false).aasm.current_state).to eq(:jailed)
|
|
411
|
+
end
|
|
366
412
|
end
|
|
367
|
-
end # direct assignment
|
|
368
413
|
|
|
369
|
-
describe '
|
|
414
|
+
describe 'transitions with persistence' do
|
|
370
415
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
416
|
+
it "should work for valid models" do
|
|
417
|
+
valid_object = Validator.create(:name => 'name')
|
|
418
|
+
expect(valid_object).to be_sleeping
|
|
419
|
+
valid_object.status = :running
|
|
420
|
+
expect(valid_object).to be_running
|
|
421
|
+
end
|
|
376
422
|
|
|
377
|
-
|
|
423
|
+
it 'should not store states for invalid models' do
|
|
424
|
+
validator = Validator.create(:name => 'name')
|
|
425
|
+
expect(validator).to be_valid
|
|
426
|
+
expect(validator).to be_sleeping
|
|
378
427
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
expect(valid_object).to be_running
|
|
384
|
-
end
|
|
428
|
+
validator.name = nil
|
|
429
|
+
expect(validator).not_to be_valid
|
|
430
|
+
expect { validator.run! }.to raise_error(ActiveRecord::RecordInvalid)
|
|
431
|
+
expect(validator).to be_sleeping
|
|
385
432
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
expect(validator).to be_sleeping
|
|
433
|
+
validator.reload
|
|
434
|
+
expect(validator).not_to be_running
|
|
435
|
+
expect(validator).to be_sleeping
|
|
390
436
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
437
|
+
validator.name = 'another name'
|
|
438
|
+
expect(validator).to be_valid
|
|
439
|
+
expect(validator.run!).to be_truthy
|
|
440
|
+
expect(validator).to be_running
|
|
395
441
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
442
|
+
validator.reload
|
|
443
|
+
expect(validator).to be_running
|
|
444
|
+
expect(validator).not_to be_sleeping
|
|
445
|
+
end
|
|
399
446
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
447
|
+
it 'should not store states for invalid models silently if configured' do
|
|
448
|
+
validator = SilentPersistor.create(:name => 'name')
|
|
449
|
+
expect(validator).to be_valid
|
|
450
|
+
expect(validator).to be_sleeping
|
|
404
451
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
452
|
+
validator.name = nil
|
|
453
|
+
expect(validator).not_to be_valid
|
|
454
|
+
expect(validator.run!).to be_falsey
|
|
455
|
+
expect(validator).to be_sleeping
|
|
456
|
+
|
|
457
|
+
validator.reload
|
|
458
|
+
expect(validator).not_to be_running
|
|
459
|
+
expect(validator).to be_sleeping
|
|
409
460
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
461
|
+
validator.name = 'another name'
|
|
462
|
+
expect(validator).to be_valid
|
|
463
|
+
expect(validator.run!).to be_truthy
|
|
464
|
+
expect(validator).to be_running
|
|
414
465
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
466
|
+
validator.reload
|
|
467
|
+
expect(validator).to be_running
|
|
468
|
+
expect(validator).not_to be_sleeping
|
|
469
|
+
end
|
|
419
470
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
471
|
+
it 'should store states for invalid models if configured' do
|
|
472
|
+
persistor = InvalidPersistor.create(:name => 'name')
|
|
473
|
+
expect(persistor).to be_valid
|
|
474
|
+
expect(persistor).to be_sleeping
|
|
423
475
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
476
|
+
persistor.name = nil
|
|
477
|
+
expect(persistor).not_to be_valid
|
|
478
|
+
expect(persistor.run!).to be_truthy
|
|
479
|
+
expect(persistor).to be_running
|
|
428
480
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
481
|
+
persistor = InvalidPersistor.find(persistor.id)
|
|
482
|
+
persistor.valid?
|
|
483
|
+
expect(persistor).to be_valid
|
|
484
|
+
expect(persistor).to be_running
|
|
485
|
+
expect(persistor).not_to be_sleeping
|
|
433
486
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
persistor.name = nil
|
|
440
|
-
expect(persistor).not_to be_valid
|
|
441
|
-
expect(persistor.run!).to be_truthy
|
|
442
|
-
expect(persistor).to be_running
|
|
443
|
-
|
|
444
|
-
persistor = InvalidPersistor.find(persistor.id)
|
|
445
|
-
persistor.valid?
|
|
446
|
-
expect(persistor).to be_valid
|
|
447
|
-
expect(persistor).to be_running
|
|
448
|
-
expect(persistor).not_to be_sleeping
|
|
449
|
-
|
|
450
|
-
persistor.reload
|
|
451
|
-
expect(persistor).to be_running
|
|
452
|
-
expect(persistor).not_to be_sleeping
|
|
453
|
-
end
|
|
487
|
+
persistor.reload
|
|
488
|
+
expect(persistor).to be_running
|
|
489
|
+
expect(persistor).not_to be_sleeping
|
|
490
|
+
end
|
|
454
491
|
|
|
455
|
-
|
|
456
|
-
|
|
492
|
+
describe 'pessimistic locking' do
|
|
493
|
+
let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') }
|
|
457
494
|
|
|
458
|
-
|
|
495
|
+
subject { transactor.run! }
|
|
459
496
|
|
|
460
|
-
|
|
461
|
-
|
|
497
|
+
context 'no lock' do
|
|
498
|
+
let(:transactor) { NoLockTransactor.create!(:name => 'no_lock_transactor', :worker => worker) }
|
|
462
499
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
500
|
+
it 'should not invoke lock!' do
|
|
501
|
+
expect(transactor).to_not receive(:lock!)
|
|
502
|
+
subject
|
|
503
|
+
end
|
|
466
504
|
end
|
|
467
|
-
end
|
|
468
505
|
|
|
469
|
-
|
|
470
|
-
|
|
506
|
+
context 'a default lock' do
|
|
507
|
+
let(:transactor) { LockTransactor.create!(:name => 'lock_transactor', :worker => worker) }
|
|
471
508
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
509
|
+
it 'should invoke lock! with true' do
|
|
510
|
+
expect(transactor).to receive(:lock!).with(true).and_call_original
|
|
511
|
+
subject
|
|
512
|
+
end
|
|
475
513
|
end
|
|
476
|
-
end
|
|
477
514
|
|
|
478
|
-
|
|
479
|
-
|
|
515
|
+
context 'a FOR UPDATE NOWAIT lock' do
|
|
516
|
+
let(:transactor) { LockNoWaitTransactor.create!(:name => 'lock_no_wait_transactor', :worker => worker) }
|
|
480
517
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
518
|
+
it 'should invoke lock! with FOR UPDATE NOWAIT' do
|
|
519
|
+
expect(transactor).to receive(:lock!).with('FOR UPDATE NOWAIT').and_call_original
|
|
520
|
+
subject
|
|
521
|
+
end
|
|
484
522
|
end
|
|
485
523
|
end
|
|
486
|
-
end
|
|
487
524
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
525
|
+
describe 'without transactions' do
|
|
526
|
+
let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') }
|
|
527
|
+
let(:no_transactor) { NoTransactor.create!(:name => 'transactor', :worker => worker) }
|
|
491
528
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
529
|
+
it 'should not rollback all changes' do
|
|
530
|
+
expect(no_transactor).to be_sleeping
|
|
531
|
+
expect(worker.status).to eq('sleeping')
|
|
495
532
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
533
|
+
expect {no_transactor.run!}.to raise_error(StandardError, 'failed on purpose')
|
|
534
|
+
expect(no_transactor).to be_running
|
|
535
|
+
expect(worker.reload.status).to eq('running')
|
|
536
|
+
end
|
|
499
537
|
end
|
|
500
538
|
|
|
501
|
-
|
|
502
|
-
|
|
539
|
+
describe 'transactions' do
|
|
540
|
+
let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') }
|
|
541
|
+
let(:transactor) { Transactor.create!(:name => 'transactor', :worker => worker) }
|
|
542
|
+
|
|
543
|
+
it 'should rollback all changes' do
|
|
503
544
|
expect(transactor).to be_sleeping
|
|
504
545
|
expect(worker.status).to eq('sleeping')
|
|
505
546
|
|
|
506
|
-
|
|
507
|
-
expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
|
|
508
|
-
end
|
|
509
|
-
|
|
547
|
+
expect {transactor.run!}.to raise_error(StandardError, 'failed on purpose')
|
|
510
548
|
expect(transactor).to be_running
|
|
511
549
|
expect(worker.reload.status).to eq('sleeping')
|
|
512
550
|
end
|
|
513
551
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
552
|
+
context "nested transactions" do
|
|
553
|
+
it "should rollback all changes in nested transaction" do
|
|
554
|
+
expect(transactor).to be_sleeping
|
|
555
|
+
expect(worker.status).to eq('sleeping')
|
|
517
556
|
|
|
518
|
-
|
|
519
|
-
|
|
557
|
+
Worker.transaction do
|
|
558
|
+
expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
|
|
559
|
+
end
|
|
520
560
|
|
|
521
|
-
|
|
522
|
-
expect
|
|
561
|
+
expect(transactor).to be_running
|
|
562
|
+
expect(worker.reload.status).to eq('sleeping')
|
|
523
563
|
end
|
|
524
564
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
end
|
|
565
|
+
it "should only rollback changes in the main transaction not the nested one" do
|
|
566
|
+
# change configuration to not require new transaction
|
|
567
|
+
AASM::StateMachineStore[Transactor][:default].config.requires_new_transaction = false
|
|
529
568
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
validator = Validator.create(:name => 'name')
|
|
533
|
-
expect(validator).to be_sleeping
|
|
569
|
+
expect(transactor).to be_sleeping
|
|
570
|
+
expect(worker.status).to eq('sleeping')
|
|
534
571
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
572
|
+
Worker.transaction do
|
|
573
|
+
expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
|
|
574
|
+
end
|
|
538
575
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
576
|
+
expect(transactor).to be_running
|
|
577
|
+
expect(worker.reload.status).to eq('running')
|
|
578
|
+
end
|
|
542
579
|
end
|
|
543
580
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
end
|
|
581
|
+
describe "after_commit callback" do
|
|
582
|
+
it "should fire :after_commit if transaction was successful" do
|
|
583
|
+
validator = Validator.create(:name => 'name')
|
|
584
|
+
expect(validator).to be_sleeping
|
|
549
585
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
expect { validator.run! }.to raise_error(ActiveRecord::RecordInvalid, 'Invalid record')
|
|
554
|
-
expect(validator).to be_sleeping
|
|
555
|
-
expect(validator.name).to eq("name")
|
|
556
|
-
end
|
|
586
|
+
validator.run!
|
|
587
|
+
expect(validator).to be_running
|
|
588
|
+
expect(validator.name).to eq("name changed")
|
|
557
589
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
expect(validator).to be_running
|
|
563
|
-
expect(validator.name).to eq("name")
|
|
564
|
-
end
|
|
565
|
-
end
|
|
590
|
+
validator.sleep!("sleeper")
|
|
591
|
+
expect(validator).to be_sleeping
|
|
592
|
+
expect(validator.name).to eq("sleeper")
|
|
593
|
+
end
|
|
566
594
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
595
|
+
it "should not fire :after_commit if transaction failed" do
|
|
596
|
+
validator = Validator.create(:name => 'name')
|
|
597
|
+
expect { validator.fail! }.to raise_error(StandardError, 'failed on purpose')
|
|
598
|
+
expect(validator.name).to eq("name")
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
it "should not fire :after_commit if validation failed when saving object" do
|
|
602
|
+
validator = Validator.create(:name => 'name')
|
|
603
|
+
validator.invalid = true
|
|
604
|
+
expect { validator.run! }.to raise_error(ActiveRecord::RecordInvalid, 'Invalid record')
|
|
605
|
+
expect(validator).to be_sleeping
|
|
606
|
+
expect(validator.name).to eq("name")
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
it "should not fire if not saving" do
|
|
610
|
+
validator = Validator.create(:name => 'name')
|
|
611
|
+
expect(validator).to be_sleeping
|
|
612
|
+
validator.run
|
|
613
|
+
expect(validator).to be_running
|
|
614
|
+
expect(validator.name).to eq("name")
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
context "nested transaction" do
|
|
618
|
+
it "should fire :after_commit if root transaction was successful" do
|
|
571
619
|
validator = Validator.create(:name => 'name')
|
|
572
620
|
expect(validator).to be_sleeping
|
|
573
621
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
622
|
+
validator.transaction do
|
|
623
|
+
validator.run!
|
|
624
|
+
expect(validator.name).to eq("name")
|
|
625
|
+
expect(validator).to be_running
|
|
626
|
+
end
|
|
577
627
|
|
|
578
|
-
|
|
579
|
-
validator
|
|
580
|
-
expect do
|
|
581
|
-
begin
|
|
582
|
-
validator.fail!
|
|
583
|
-
rescue => ignored
|
|
584
|
-
end
|
|
585
|
-
end.to change { validator.send("#{event_type}_transaction_performed_on_fail") }.from(nil).to(true)
|
|
586
|
-
expect(validator).to_not be_running
|
|
628
|
+
expect(validator.name).to eq("name changed")
|
|
629
|
+
expect(validator.reload).to be_running
|
|
587
630
|
end
|
|
588
631
|
|
|
589
|
-
it "should not fire
|
|
632
|
+
it "should not fire :after_commit if root transaction failed" do
|
|
590
633
|
validator = Validator.create(:name => 'name')
|
|
591
634
|
expect(validator).to be_sleeping
|
|
592
|
-
|
|
593
|
-
|
|
635
|
+
|
|
636
|
+
validator.transaction do
|
|
637
|
+
validator.run!
|
|
638
|
+
expect(validator.name).to eq("name")
|
|
639
|
+
expect(validator).to be_running
|
|
640
|
+
|
|
641
|
+
raise ActiveRecord::Rollback, "failed on purpose"
|
|
642
|
+
end
|
|
643
|
+
|
|
594
644
|
expect(validator.name).to eq("name")
|
|
645
|
+
expect(validator.reload).to be_sleeping
|
|
595
646
|
end
|
|
596
647
|
end
|
|
597
648
|
end
|
|
598
|
-
end
|
|
599
|
-
|
|
600
|
-
describe 'before and after all transactions callbacks' do
|
|
601
|
-
[:after, :before].each do |event_type|
|
|
602
|
-
describe "#{event_type}_all_transactions callback" do
|
|
603
|
-
it "should fire :#{event_type}_all_transactions if transaction was successful" do
|
|
604
|
-
validator = Validator.create(:name => 'name')
|
|
605
|
-
expect(validator).to be_sleeping
|
|
606
649
|
|
|
607
|
-
|
|
608
|
-
|
|
650
|
+
describe 'callbacks for the new DSL' do
|
|
651
|
+
|
|
652
|
+
it "be called in order" do
|
|
653
|
+
show_debug_log = false
|
|
654
|
+
|
|
655
|
+
callback = ActiveRecordCallback.create
|
|
656
|
+
callback.aasm.current_state
|
|
657
|
+
|
|
658
|
+
unless show_debug_log
|
|
659
|
+
expect(callback).to receive(:before_all_events).once.ordered
|
|
660
|
+
expect(callback).to receive(:before_event).once.ordered
|
|
661
|
+
expect(callback).to receive(:event_guard).once.ordered.and_return(true)
|
|
662
|
+
expect(callback).to receive(:transition_guard).once.ordered.and_return(true)
|
|
663
|
+
expect(callback).to receive(:before_exit_open).once.ordered # these should be before the state changes
|
|
664
|
+
expect(callback).to receive(:exit_open).once.ordered
|
|
665
|
+
# expect(callback).to receive(:event_guard).once.ordered.and_return(true)
|
|
666
|
+
# expect(callback).to receive(:transition_guard).once.ordered.and_return(true)
|
|
667
|
+
expect(callback).to receive(:after_all_transitions).once.ordered
|
|
668
|
+
expect(callback).to receive(:after_transition).once.ordered
|
|
669
|
+
expect(callback).to receive(:before_enter_closed).once.ordered
|
|
670
|
+
expect(callback).to receive(:enter_closed).once.ordered
|
|
671
|
+
expect(callback).to receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes
|
|
672
|
+
expect(callback).to receive(:after_exit_open).once.ordered # these should be after the state changes
|
|
673
|
+
expect(callback).to receive(:after_enter_closed).once.ordered
|
|
674
|
+
expect(callback).to receive(:after_event).once.ordered
|
|
675
|
+
expect(callback).to receive(:after_all_events).once.ordered
|
|
676
|
+
expect(callback).to receive(:ensure_event).once.ordered
|
|
677
|
+
expect(callback).to receive(:ensure_on_all_events).once.ordered
|
|
678
|
+
expect(callback).to receive(:event_after_commit).once.ordered
|
|
609
679
|
end
|
|
610
680
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
681
|
+
callback.close!
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
describe 'before and after transaction callbacks' do
|
|
686
|
+
[:after, :before].each do |event_type|
|
|
687
|
+
describe "#{event_type}_transaction callback" do
|
|
688
|
+
it "should fire :#{event_type}_transaction if transaction was successful" do
|
|
689
|
+
validator = Validator.create(:name => 'name')
|
|
690
|
+
expect(validator).to be_sleeping
|
|
691
|
+
|
|
692
|
+
expect { validator.run! }.to change { validator.send("#{event_type}_transaction_performed_on_run") }.from(nil).to(true)
|
|
693
|
+
expect(validator).to be_running
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
it "should fire :#{event_type}_transaction if transaction failed" do
|
|
697
|
+
validator = Validator.create(:name => 'name')
|
|
698
|
+
expect do
|
|
699
|
+
begin
|
|
700
|
+
validator.fail!
|
|
701
|
+
rescue => ignored
|
|
702
|
+
end
|
|
703
|
+
end.to change { validator.send("#{event_type}_transaction_performed_on_fail") }.from(nil).to(true)
|
|
704
|
+
expect(validator).to_not be_running
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
it "should not fire :#{event_type}_transaction if not saving" do
|
|
708
|
+
validator = Validator.create(:name => 'name')
|
|
709
|
+
expect(validator).to be_sleeping
|
|
710
|
+
expect { validator.run }.to_not change { validator.send("#{event_type}_transaction_performed_on_run") }
|
|
711
|
+
expect(validator).to be_running
|
|
712
|
+
expect(validator.name).to eq("name")
|
|
713
|
+
end
|
|
620
714
|
end
|
|
715
|
+
end
|
|
716
|
+
end
|
|
621
717
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
718
|
+
describe 'before and after all transactions callbacks' do
|
|
719
|
+
[:after, :before].each do |event_type|
|
|
720
|
+
describe "#{event_type}_all_transactions callback" do
|
|
721
|
+
it "should fire :#{event_type}_all_transactions if transaction was successful" do
|
|
722
|
+
validator = Validator.create(:name => 'name')
|
|
723
|
+
expect(validator).to be_sleeping
|
|
724
|
+
|
|
725
|
+
expect { validator.run! }.to change { validator.send("#{event_type}_all_transactions_performed") }.from(nil).to(true)
|
|
726
|
+
expect(validator).to be_running
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
it "should fire :#{event_type}_all_transactions if transaction failed" do
|
|
730
|
+
validator = Validator.create(:name => 'name')
|
|
731
|
+
expect do
|
|
732
|
+
begin
|
|
733
|
+
validator.fail!
|
|
734
|
+
rescue => ignored
|
|
735
|
+
end
|
|
736
|
+
end.to change { validator.send("#{event_type}_all_transactions_performed") }.from(nil).to(true)
|
|
737
|
+
expect(validator).to_not be_running
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
it "should not fire :#{event_type}_all_transactions if not saving" do
|
|
741
|
+
validator = Validator.create(:name => 'name')
|
|
742
|
+
expect(validator).to be_sleeping
|
|
743
|
+
expect { validator.run }.to_not change { validator.send("#{event_type}_all_transactions_performed") }
|
|
744
|
+
expect(validator).to be_running
|
|
745
|
+
expect(validator.name).to eq("name")
|
|
746
|
+
end
|
|
628
747
|
end
|
|
629
748
|
end
|
|
630
749
|
end
|
|
631
|
-
end
|
|
632
750
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
751
|
+
context "when not persisting" do
|
|
752
|
+
it 'should not rollback all changes' do
|
|
753
|
+
expect(transactor).to be_sleeping
|
|
754
|
+
expect(worker.status).to eq('sleeping')
|
|
637
755
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
756
|
+
# Notice here we're calling "run" and not "run!" with a bang.
|
|
757
|
+
expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
|
|
758
|
+
expect(transactor).to be_running
|
|
759
|
+
expect(worker.reload.status).to eq('running')
|
|
760
|
+
end
|
|
643
761
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
762
|
+
it 'should not create a database transaction' do
|
|
763
|
+
expect(transactor.class).not_to receive(:transaction)
|
|
764
|
+
expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
|
|
765
|
+
end
|
|
647
766
|
end
|
|
648
767
|
end
|
|
649
768
|
end
|
|
650
|
-
end
|
|
651
769
|
|
|
652
|
-
describe "invalid states with persistence" do
|
|
770
|
+
describe "invalid states with persistence" do
|
|
653
771
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
772
|
+
it "should not store states" do
|
|
773
|
+
validator = Validator.create(:name => 'name')
|
|
774
|
+
validator.status = 'invalid_state'
|
|
775
|
+
expect(validator.save).to be_falsey
|
|
776
|
+
expect {validator.save!}.to raise_error(ActiveRecord::RecordInvalid)
|
|
777
|
+
|
|
778
|
+
validator.reload
|
|
779
|
+
expect(validator).to be_sleeping
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
it "should store invalid states if configured" do
|
|
783
|
+
persistor = InvalidPersistor.create(:name => 'name')
|
|
784
|
+
persistor.status = 'invalid_state'
|
|
785
|
+
expect(persistor.save).to be_truthy
|
|
786
|
+
|
|
787
|
+
persistor.reload
|
|
788
|
+
expect(persistor.status).to eq('invalid_state')
|
|
789
|
+
end
|
|
659
790
|
|
|
660
|
-
validator.reload
|
|
661
|
-
expect(validator).to be_sleeping
|
|
662
791
|
end
|
|
663
792
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
persistor.status = 'invalid_state'
|
|
667
|
-
expect(persistor.save).to be_truthy
|
|
793
|
+
describe 'basic example with two state machines' do
|
|
794
|
+
let(:example) { BasicActiveRecordTwoStateMachinesExample.new }
|
|
668
795
|
|
|
669
|
-
|
|
670
|
-
|
|
796
|
+
it 'should initialise properly' do
|
|
797
|
+
expect(example.aasm(:search).current_state).to eql :initialised
|
|
798
|
+
expect(example.aasm(:sync).current_state).to eql :unsynced
|
|
799
|
+
end
|
|
671
800
|
end
|
|
672
801
|
|
|
673
|
-
|
|
802
|
+
describe 'testing the README examples' do
|
|
803
|
+
it 'Usage' do
|
|
804
|
+
job = ReadmeJob.new
|
|
805
|
+
|
|
806
|
+
expect(job.sleeping?).to eql true
|
|
807
|
+
expect(job.may_run?).to eql true
|
|
808
|
+
|
|
809
|
+
job.run
|
|
674
810
|
|
|
675
|
-
|
|
676
|
-
|
|
811
|
+
expect(job.running?).to eql true
|
|
812
|
+
expect(job.sleeping?).to eql false
|
|
813
|
+
expect(job.may_run?).to eql false
|
|
677
814
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
expect(example.aasm(:sync).current_state).to eql :unsynced
|
|
815
|
+
expect { job.run }.to raise_error(AASM::InvalidTransition)
|
|
816
|
+
end
|
|
681
817
|
end
|
|
682
|
-
end
|
|
683
818
|
|
|
684
|
-
describe 'testing the
|
|
685
|
-
|
|
686
|
-
|
|
819
|
+
describe 'testing the instance_level skip validation with _without_validation method' do
|
|
820
|
+
let(:example) do
|
|
821
|
+
obj = InstanceLevelSkipValidationExample.new(state: 'new')
|
|
822
|
+
obj.save(validate: false)
|
|
823
|
+
obj
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
it 'should be able to change the state with invalid record' do
|
|
827
|
+
expect(example.valid?).to be_falsey
|
|
828
|
+
expect(example.complete!).to be_falsey
|
|
829
|
+
expect(example.complete_without_validation!).to be_truthy
|
|
830
|
+
expect(example.state).to eq('complete')
|
|
831
|
+
end
|
|
687
832
|
|
|
688
|
-
|
|
689
|
-
|
|
833
|
+
it 'shouldn\'t affect the behaviour of existing method after calling _without_validation! method' do
|
|
834
|
+
expect(example.set_draft!).to be_falsey
|
|
835
|
+
expect(example.set_draft_without_validation!).to be_truthy
|
|
836
|
+
expect(example.state).to eq('draft')
|
|
837
|
+
expect(example.complete!).to be_falsey
|
|
838
|
+
end
|
|
839
|
+
end
|
|
690
840
|
|
|
691
|
-
|
|
841
|
+
describe 'testing the timestamps option' do
|
|
842
|
+
let(:example) { TimestampExample.create! }
|
|
692
843
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
844
|
+
it 'should update existing timestamp columns' do
|
|
845
|
+
expect { example.open! }.to change { example.reload.opened_at }.from(nil).to(instance_of(::Time))
|
|
846
|
+
end
|
|
696
847
|
|
|
697
|
-
|
|
848
|
+
it 'should not fail if there is no corresponding timestamp column' do
|
|
849
|
+
expect { example.close! }.to change { example.reload.aasm_state }
|
|
850
|
+
end
|
|
698
851
|
end
|
|
699
852
|
end
|