statesman 4.1.4 → 5.0.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +23 -38
- data/lib/statesman/adapters/active_record_queries.rb +96 -34
- data/lib/statesman/version.rb +1 -1
- data/spec/statesman/adapters/active_record_queries_spec.rb +152 -114
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f79354b244926e7be655b02df501cfed437ab2da6e869ec8db35097bb5ef372
|
4
|
+
data.tar.gz: 2b6ad54a68b6bca463c00c9e09a83d2d2d9d47d984fad212da77b0b0fd8a18a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e66962ac0dd871627bdf6c53c5282e140bd109c3fcd0ba2ebd25504c490e3ddf2050132aabdf143c35aff09330e229afe8ec57c7d7fc43552bcff8140f8f0fa0
|
7
|
+
data.tar.gz: 9e3fa636071bcd530b177e80bf1014071d38601da2076e5258ffed22f41eec388255db2b03b79710130624c5fb6c60009af511b76d478be718f5fc69a6f0e933
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
## v5.0.0, 11th November 2019
|
2
|
+
|
3
|
+
- Adds new syntax and restrictions to ActiveRecordQueries [PR#358](https://github.com/gocardless/statesman/pull/358). With the introduction of this, defining `self.transition_class` or `self.initial_state` is deprecated and will be removed in the next major release.
|
4
|
+
|
1
5
|
## v4.1.4, 11th November 2019
|
2
6
|
|
3
7
|
- Reverts the breaking changes from [PR#358](https://github.com/gocardless/statesman/pull/358) & `v4.1.3` that where included in the last minor release. If you have changed your code to work with these changes `v5.0.0` will be a copy of `v4.1.3` with a bugfix applied.
|
data/README.md
CHANGED
@@ -30,7 +30,7 @@ protection.
|
|
30
30
|
To get started, just add Statesman to your `Gemfile`, and then run `bundle`:
|
31
31
|
|
32
32
|
```ruby
|
33
|
-
gem 'statesman', '~>
|
33
|
+
gem 'statesman', '~> 5.0.0'
|
34
34
|
```
|
35
35
|
|
36
36
|
## Usage
|
@@ -76,22 +76,16 @@ Then, link it to your model:
|
|
76
76
|
|
77
77
|
```ruby
|
78
78
|
class Order < ActiveRecord::Base
|
79
|
-
include Statesman::Adapters::ActiveRecordQueries
|
80
|
-
|
81
79
|
has_many :order_transitions, autosave: false
|
82
80
|
|
81
|
+
include Statesman::Adapters::ActiveRecordQueries[
|
82
|
+
transition_class: OrderTransition,
|
83
|
+
initial_state: :pending
|
84
|
+
]
|
85
|
+
|
83
86
|
def state_machine
|
84
87
|
@state_machine ||= OrderStateMachine.new(self, transition_class: OrderTransition)
|
85
88
|
end
|
86
|
-
|
87
|
-
def self.transition_class
|
88
|
-
OrderTransition
|
89
|
-
end
|
90
|
-
|
91
|
-
def self.initial_state
|
92
|
-
:pending
|
93
|
-
end
|
94
|
-
private_class_method :initial_state
|
95
89
|
end
|
96
90
|
```
|
97
91
|
|
@@ -357,43 +351,34 @@ callback code throws an exception, it will not be caught.)
|
|
357
351
|
|
358
352
|
A mixin is provided for the ActiveRecord adapter which adds scopes to easily
|
359
353
|
find all models currently in (or not in) a given state. Include it into your
|
360
|
-
model and
|
354
|
+
model and passing in `transition_class` and `initial_state` as options.
|
355
|
+
|
356
|
+
In 4.1.2 and below, these two options had to be defined as methods on the model,
|
357
|
+
but 5.0.0 and above allow this style of configuration as well.
|
358
|
+
The old method pollutes the model with extra class methods, and is deprecated,
|
359
|
+
to be removed in 6.0.0.
|
361
360
|
|
362
361
|
```ruby
|
363
362
|
class Order < ActiveRecord::Base
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
private_class_method :transition_class
|
370
|
-
|
371
|
-
def self.initial_state
|
372
|
-
OrderStateMachine.initial_state
|
373
|
-
end
|
374
|
-
private_class_method :initial_state
|
363
|
+
has_many :order_transitions, autosave: false
|
364
|
+
include Statesman::Adapters::ActiveRecordQueries[
|
365
|
+
transition_class: OrderTransition,
|
366
|
+
initial_state: OrderStateMachine.initial_state
|
367
|
+
]
|
375
368
|
end
|
376
369
|
```
|
377
370
|
|
378
371
|
If the transition class-name differs from the association name, you will also
|
379
|
-
need to
|
372
|
+
need to pass `transition_name` as an option:
|
380
373
|
|
381
374
|
```ruby
|
382
375
|
class Order < ActiveRecord::Base
|
383
376
|
has_many :transitions, class_name: "OrderTransition", autosave: false
|
384
|
-
|
385
|
-
|
386
|
-
:
|
387
|
-
|
388
|
-
|
389
|
-
def self.transition_class
|
390
|
-
OrderTransition
|
391
|
-
end
|
392
|
-
|
393
|
-
def self.initial_state
|
394
|
-
OrderStateMachine.initial_state
|
395
|
-
end
|
396
|
-
private_class_method :initial_state
|
377
|
+
include Statesman::Adapters::ActiveRecordQueries[
|
378
|
+
transition_class: OrderTransition,
|
379
|
+
initial_state: OrderStateMachine.initial_state,
|
380
|
+
transition_name: :transitions
|
381
|
+
]
|
397
382
|
end
|
398
383
|
```
|
399
384
|
|
@@ -1,51 +1,122 @@
|
|
1
1
|
module Statesman
|
2
2
|
module Adapters
|
3
3
|
module ActiveRecordQueries
|
4
|
+
def self.check_missing_methods!(base)
|
5
|
+
missing_methods = %i[transition_class initial_state].
|
6
|
+
reject { |m| base.respond_to?(m) }
|
7
|
+
return if missing_methods.none?
|
8
|
+
|
9
|
+
raise NotImplementedError,
|
10
|
+
"#{missing_methods.join(', ')} method(s) should be defined on " \
|
11
|
+
"the model. Alternatively, use the new form of `extend " \
|
12
|
+
"Statesman::Adapters::ActiveRecordQueries[" \
|
13
|
+
"transition_class: MyTransition, " \
|
14
|
+
"initial_state: :some_state]`"
|
15
|
+
end
|
16
|
+
|
4
17
|
def self.included(base)
|
5
|
-
base
|
18
|
+
check_missing_methods!(base)
|
19
|
+
|
20
|
+
base.include(
|
21
|
+
ClassMethods.new(
|
22
|
+
transition_class: base.transition_class,
|
23
|
+
initial_state: base.initial_state,
|
24
|
+
most_recent_transition_alias: base.try(:most_recent_transition_alias),
|
25
|
+
transition_name: base.try(:transition_name),
|
26
|
+
),
|
27
|
+
)
|
6
28
|
end
|
7
29
|
|
8
|
-
|
9
|
-
|
10
|
-
|
30
|
+
def self.[](**args)
|
31
|
+
ClassMethods.new(**args)
|
32
|
+
end
|
33
|
+
|
34
|
+
class ClassMethods < Module
|
35
|
+
def initialize(**args)
|
36
|
+
@args = args
|
37
|
+
end
|
38
|
+
|
39
|
+
def included(base)
|
40
|
+
ensure_inheritance(base)
|
41
|
+
|
42
|
+
query_builder = QueryBuilder.new(base, **@args)
|
43
|
+
|
44
|
+
base.define_singleton_method(:most_recent_transition_join) do
|
45
|
+
query_builder.most_recent_transition_join
|
46
|
+
end
|
47
|
+
|
48
|
+
define_in_state(base, query_builder)
|
49
|
+
define_not_in_state(base, query_builder)
|
50
|
+
end
|
11
51
|
|
12
|
-
|
13
|
-
|
52
|
+
private
|
53
|
+
|
54
|
+
def ensure_inheritance(base)
|
55
|
+
klass = self
|
56
|
+
existing_inherited = base.method(:inherited)
|
57
|
+
base.define_singleton_method(:inherited) do |subclass|
|
58
|
+
existing_inherited.call(subclass)
|
59
|
+
subclass.send(:include, klass)
|
60
|
+
end
|
14
61
|
end
|
15
62
|
|
16
|
-
def
|
17
|
-
|
63
|
+
def define_in_state(base, query_builder)
|
64
|
+
base.define_singleton_method(:in_state) do |*states|
|
65
|
+
states = states.flatten.map(&:to_s)
|
18
66
|
|
19
|
-
|
20
|
-
|
21
|
-
|
67
|
+
joins(most_recent_transition_join).
|
68
|
+
where(query_builder.states_where(states), states)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def define_not_in_state(base, query_builder)
|
73
|
+
base.define_singleton_method(:not_in_state) do |*states|
|
74
|
+
states = states.flatten.map(&:to_s)
|
75
|
+
|
76
|
+
joins(most_recent_transition_join).
|
77
|
+
where("NOT (#{query_builder.states_where(states)})", states)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class QueryBuilder
|
83
|
+
def initialize(model, transition_class:, initial_state:,
|
84
|
+
most_recent_transition_alias: nil,
|
85
|
+
transition_name: nil)
|
86
|
+
@model = model
|
87
|
+
@transition_class = transition_class
|
88
|
+
@initial_state = initial_state
|
89
|
+
@most_recent_transition_alias = most_recent_transition_alias
|
90
|
+
@transition_name = transition_name
|
91
|
+
end
|
92
|
+
|
93
|
+
def states_where(states)
|
94
|
+
if initial_state.to_s.in?(states.map(&:to_s))
|
95
|
+
"#{most_recent_transition_alias}.to_state IN (?) OR " \
|
96
|
+
"#{most_recent_transition_alias}.to_state IS NULL"
|
97
|
+
else
|
98
|
+
"#{most_recent_transition_alias}.to_state IN (?) AND " \
|
99
|
+
"#{most_recent_transition_alias}.to_state IS NOT NULL"
|
100
|
+
end
|
22
101
|
end
|
23
102
|
|
24
103
|
def most_recent_transition_join
|
25
104
|
"LEFT OUTER JOIN #{model_table} AS #{most_recent_transition_alias}
|
26
|
-
ON #{table_name}.id =
|
105
|
+
ON #{model.table_name}.id =
|
27
106
|
#{most_recent_transition_alias}.#{model_foreign_key}
|
28
107
|
AND #{most_recent_transition_alias}.most_recent = #{db_true}"
|
29
108
|
end
|
30
109
|
|
31
110
|
private
|
32
111
|
|
33
|
-
|
34
|
-
raise NotImplementedError, "A transition_class method should be " \
|
35
|
-
"defined on the model"
|
36
|
-
end
|
37
|
-
|
38
|
-
def initial_state
|
39
|
-
raise NotImplementedError, "An initial_state method should be " \
|
40
|
-
"defined on the model"
|
41
|
-
end
|
112
|
+
attr_reader :model, :transition_class, :initial_state
|
42
113
|
|
43
114
|
def transition_name
|
44
|
-
transition_class.table_name.to_sym
|
115
|
+
@transition_name || transition_class.table_name.to_sym
|
45
116
|
end
|
46
117
|
|
47
118
|
def transition_reflection
|
48
|
-
reflect_on_all_associations(:has_many).each do |value|
|
119
|
+
model.reflect_on_all_associations(:has_many).each do |value|
|
49
120
|
return value if value.klass == transition_class
|
50
121
|
end
|
51
122
|
|
@@ -62,18 +133,9 @@ module Statesman
|
|
62
133
|
transition_reflection.table_name
|
63
134
|
end
|
64
135
|
|
65
|
-
def states_where(temporary_table_name, states)
|
66
|
-
if initial_state.to_s.in?(states.map(&:to_s))
|
67
|
-
"#{temporary_table_name}.to_state IN (?) OR " \
|
68
|
-
"#{temporary_table_name}.to_state IS NULL"
|
69
|
-
else
|
70
|
-
"#{temporary_table_name}.to_state IN (?) AND " \
|
71
|
-
"#{temporary_table_name}.to_state IS NOT NULL"
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
136
|
def most_recent_transition_alias
|
76
|
-
|
137
|
+
@most_recent_transition_alias ||
|
138
|
+
"most_recent_#{transition_name.to_s.singularize}"
|
77
139
|
end
|
78
140
|
|
79
141
|
def db_true
|
data/lib/statesman/version.rb
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
4
|
+
def configure_old(klass, transition_class)
|
5
|
+
klass.define_singleton_method(:transition_class) { transition_class }
|
6
|
+
klass.define_singleton_method(:initial_state) { :initial }
|
7
|
+
klass.send(:include, described_class)
|
8
|
+
end
|
9
|
+
|
10
|
+
def configure_new(klass, transition_class)
|
11
|
+
klass.send(:include, described_class[transition_class: transition_class,
|
12
|
+
initial_state: :initial])
|
13
|
+
end
|
14
|
+
|
4
15
|
before do
|
5
16
|
prepare_model_table
|
6
17
|
prepare_transitions_table
|
@@ -8,32 +19,6 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
8
19
|
prepare_other_transitions_table
|
9
20
|
|
10
21
|
Statesman.configure { storage_adapter(Statesman::Adapters::ActiveRecord) }
|
11
|
-
|
12
|
-
MyActiveRecordModel.send(:include, Statesman::Adapters::ActiveRecordQueries)
|
13
|
-
MyActiveRecordModel.class_eval do
|
14
|
-
def self.transition_class
|
15
|
-
MyActiveRecordModelTransition
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.initial_state
|
19
|
-
:initial
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
OtherActiveRecordModel.send(:include,
|
24
|
-
Statesman::Adapters::ActiveRecordQueries)
|
25
|
-
OtherActiveRecordModel.class_eval do
|
26
|
-
def self.transition_class
|
27
|
-
OtherActiveRecordModelTransition
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.initial_state
|
31
|
-
:initial
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
MyActiveRecordModel.send(:has_one, :other_active_record_model)
|
36
|
-
OtherActiveRecordModel.send(:belongs_to, :my_active_record_model)
|
37
22
|
end
|
38
23
|
|
39
24
|
after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
|
@@ -59,105 +44,164 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
59
44
|
model
|
60
45
|
end
|
61
46
|
|
62
|
-
|
63
|
-
|
64
|
-
|
47
|
+
shared_examples "testing methods" do
|
48
|
+
before do
|
49
|
+
if config_type == :old
|
50
|
+
configure_old(MyActiveRecordModel, MyActiveRecordModelTransition)
|
51
|
+
configure_old(OtherActiveRecordModel, OtherActiveRecordModelTransition)
|
52
|
+
elsif config_type == :new
|
53
|
+
configure_new(MyActiveRecordModel, MyActiveRecordModelTransition)
|
54
|
+
configure_new(OtherActiveRecordModel, OtherActiveRecordModelTransition)
|
55
|
+
else
|
56
|
+
raise "Unknown config type #{config_type}"
|
57
|
+
end
|
65
58
|
|
66
|
-
|
67
|
-
|
59
|
+
MyActiveRecordModel.send(:has_one, :other_active_record_model)
|
60
|
+
OtherActiveRecordModel.send(:belongs_to, :my_active_record_model)
|
68
61
|
end
|
69
62
|
|
70
|
-
|
71
|
-
|
63
|
+
describe ".in_state" do
|
64
|
+
context "given a single state" do
|
65
|
+
subject { MyActiveRecordModel.in_state(:succeeded) }
|
72
66
|
|
73
|
-
|
74
|
-
|
75
|
-
|
67
|
+
it { is_expected.to include model }
|
68
|
+
it { is_expected.to_not include other_model }
|
69
|
+
end
|
76
70
|
|
77
|
-
|
78
|
-
|
71
|
+
context "given multiple states" do
|
72
|
+
subject { MyActiveRecordModel.in_state(:succeeded, :failed) }
|
79
73
|
|
80
|
-
|
81
|
-
|
82
|
-
|
74
|
+
it { is_expected.to include model }
|
75
|
+
it { is_expected.to include other_model }
|
76
|
+
end
|
83
77
|
|
84
|
-
|
85
|
-
|
78
|
+
context "given the initial state" do
|
79
|
+
subject { MyActiveRecordModel.in_state(:initial) }
|
86
80
|
|
87
|
-
|
88
|
-
|
89
|
-
|
81
|
+
it { is_expected.to include initial_state_model }
|
82
|
+
it { is_expected.to include returned_to_initial_model }
|
83
|
+
end
|
84
|
+
|
85
|
+
context "given an array of states" do
|
86
|
+
subject { MyActiveRecordModel.in_state(%i[succeeded failed]) }
|
90
87
|
|
91
|
-
|
92
|
-
|
93
|
-
MyActiveRecordModel.in_state(:succeeded).
|
94
|
-
joins(:other_active_record_model).
|
95
|
-
merge(OtherActiveRecordModel.in_state(:initial))
|
88
|
+
it { is_expected.to include model }
|
89
|
+
it { is_expected.to include other_model }
|
96
90
|
end
|
97
91
|
|
98
|
-
|
92
|
+
context "merging two queries" do
|
93
|
+
subject do
|
94
|
+
MyActiveRecordModel.in_state(:succeeded).
|
95
|
+
joins(:other_active_record_model).
|
96
|
+
merge(OtherActiveRecordModel.in_state(:initial))
|
97
|
+
end
|
98
|
+
|
99
|
+
it { is_expected.to be_empty }
|
100
|
+
end
|
99
101
|
end
|
100
|
-
end
|
101
102
|
|
102
|
-
|
103
|
-
|
104
|
-
|
103
|
+
describe ".not_in_state" do
|
104
|
+
context "given a single state" do
|
105
|
+
subject { MyActiveRecordModel.not_in_state(:failed) }
|
105
106
|
|
106
|
-
|
107
|
-
|
108
|
-
|
107
|
+
it { is_expected.to include model }
|
108
|
+
it { is_expected.to_not include other_model }
|
109
|
+
end
|
109
110
|
|
110
|
-
|
111
|
-
|
111
|
+
context "given multiple states" do
|
112
|
+
subject(:not_in_state) { MyActiveRecordModel.not_in_state(:succeeded, :failed) }
|
112
113
|
|
113
|
-
|
114
|
-
|
115
|
-
|
114
|
+
it do
|
115
|
+
expect(not_in_state).to match_array([initial_state_model,
|
116
|
+
returned_to_initial_model])
|
117
|
+
end
|
116
118
|
end
|
117
|
-
end
|
118
119
|
|
119
|
-
|
120
|
-
|
120
|
+
context "given an array of states" do
|
121
|
+
subject(:not_in_state) { MyActiveRecordModel.not_in_state(%i[succeeded failed]) }
|
121
122
|
|
122
|
-
|
123
|
-
|
124
|
-
|
123
|
+
it do
|
124
|
+
expect(not_in_state).to match_array([initial_state_model,
|
125
|
+
returned_to_initial_model])
|
126
|
+
end
|
125
127
|
end
|
126
128
|
end
|
127
|
-
end
|
128
129
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
130
|
+
context "with a custom name for the transition association" do
|
131
|
+
before do
|
132
|
+
# Switch to using OtherActiveRecordModelTransition, so the existing
|
133
|
+
# relation with MyActiveRecordModelTransition doesn't interfere with
|
134
|
+
# this spec.
|
135
|
+
MyActiveRecordModel.send(:has_many,
|
136
|
+
:custom_name,
|
137
|
+
class_name: "OtherActiveRecordModelTransition")
|
138
|
+
|
139
|
+
MyActiveRecordModel.class_eval do
|
140
|
+
def self.transition_class
|
141
|
+
OtherActiveRecordModelTransition
|
142
|
+
end
|
141
143
|
end
|
142
144
|
end
|
145
|
+
|
146
|
+
describe ".in_state" do
|
147
|
+
subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
|
148
|
+
|
149
|
+
specify { expect { query }.to_not raise_error }
|
150
|
+
end
|
143
151
|
end
|
144
152
|
|
145
|
-
|
146
|
-
|
153
|
+
context "after_commit transactional integrity" do
|
154
|
+
before do
|
155
|
+
MyStateMachine.class_eval do
|
156
|
+
cattr_accessor(:after_commit_callback_executed) { false }
|
157
|
+
|
158
|
+
after_transition(from: :initial, to: :succeeded, after_commit: true) do
|
159
|
+
# This leaks state in a testable way if transactional integrity is broken.
|
160
|
+
MyStateMachine.after_commit_callback_executed = true
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
after do
|
166
|
+
MyStateMachine.class_eval do
|
167
|
+
callbacks[:after_commit] = []
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
let!(:model) do
|
172
|
+
MyActiveRecordModel.create
|
173
|
+
end
|
147
174
|
|
148
|
-
|
175
|
+
# rubocop:disable RSpec/ExampleLength
|
176
|
+
it do
|
177
|
+
expect do
|
178
|
+
ActiveRecord::Base.transaction do
|
179
|
+
model.state_machine.transition_to!(:succeeded)
|
180
|
+
raise ActiveRecord::Rollback
|
181
|
+
end
|
182
|
+
end.to_not change(MyStateMachine, :after_commit_callback_executed)
|
183
|
+
end
|
184
|
+
# rubocop:enable RSpec/ExampleLength
|
149
185
|
end
|
150
186
|
end
|
151
187
|
|
188
|
+
context "using old configuration method" do
|
189
|
+
let(:config_type) { :old }
|
190
|
+
|
191
|
+
include_examples "testing methods"
|
192
|
+
end
|
193
|
+
|
194
|
+
context "using new configuration method" do
|
195
|
+
let(:config_type) { :new }
|
196
|
+
|
197
|
+
include_examples "testing methods"
|
198
|
+
end
|
199
|
+
|
152
200
|
context "with no association with the transition class" do
|
153
201
|
before do
|
154
202
|
class UnknownModelTransition < OtherActiveRecordModelTransition; end
|
155
203
|
|
156
|
-
MyActiveRecordModel
|
157
|
-
def self.transition_class
|
158
|
-
UnknownModelTransition
|
159
|
-
end
|
160
|
-
end
|
204
|
+
configure_old(MyActiveRecordModel, UnknownModelTransition)
|
161
205
|
end
|
162
206
|
|
163
207
|
describe ".in_state" do
|
@@ -169,37 +213,31 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do
|
|
169
213
|
end
|
170
214
|
end
|
171
215
|
|
172
|
-
|
173
|
-
|
174
|
-
MyStateMachine.class_eval do
|
175
|
-
cattr_accessor(:after_commit_callback_executed) { false }
|
216
|
+
describe "check_missing_methods!" do
|
217
|
+
subject(:check_missing_methods!) { described_class.check_missing_methods!(base) }
|
176
218
|
|
177
|
-
|
178
|
-
|
179
|
-
|
219
|
+
context "when base has no missing methods" do
|
220
|
+
let(:base) do
|
221
|
+
Class.new do
|
222
|
+
def self.transition_class; end
|
223
|
+
|
224
|
+
def self.initial_state; end
|
180
225
|
end
|
181
226
|
end
|
182
|
-
end
|
183
227
|
|
184
|
-
|
185
|
-
|
186
|
-
callbacks[:after_commit] = []
|
228
|
+
it "does not raise an error" do
|
229
|
+
expect { check_missing_methods! }.to_not raise_exception(NotImplementedError)
|
187
230
|
end
|
188
231
|
end
|
189
232
|
|
190
|
-
|
191
|
-
|
192
|
-
|
233
|
+
context "when base has missing methods" do
|
234
|
+
let(:base) do
|
235
|
+
Class.new
|
236
|
+
end
|
193
237
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
ActiveRecord::Base.transaction do
|
198
|
-
model.state_machine.transition_to!(:succeeded)
|
199
|
-
raise ActiveRecord::Rollback
|
200
|
-
end
|
201
|
-
end.to_not change(MyStateMachine, :after_commit_callback_executed)
|
238
|
+
it "raises an error" do
|
239
|
+
expect { check_missing_methods! }.to raise_exception(NotImplementedError)
|
240
|
+
end
|
202
241
|
end
|
203
|
-
# rubocop:enable RSpec/ExampleLength
|
204
242
|
end
|
205
243
|
end
|