state_gate 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/state_gate/builder/conflict_detection_methods.rb +59 -18
- data/lib/state_gate/builder/dynamic_module_creation_methods.rb +36 -22
- data/lib/state_gate/builder/scope_methods.rb +30 -20
- data/lib/state_gate/builder/state_methods.rb +108 -72
- data/lib/state_gate/builder/transition_methods.rb +54 -34
- data/lib/state_gate/builder/transition_validation_methods.rb +76 -66
- data/lib/state_gate/builder.rb +53 -43
- data/lib/state_gate/engine/configurator.rb +77 -63
- data/lib/state_gate/engine/errator.rb +42 -9
- data/lib/state_gate/engine/fixer.rb +21 -12
- data/lib/state_gate/engine/scoper.rb +12 -4
- data/lib/state_gate/engine/sequencer.rb +18 -5
- data/lib/state_gate/engine/stator.rb +98 -53
- data/lib/state_gate/engine/transitioner.rb +28 -14
- data/lib/state_gate/engine.rb +19 -6
- data/lib/state_gate/type.rb +22 -2
- data/lib/state_gate.rb +88 -61
- metadata +3 -3
@@ -8,58 +8,58 @@ module StateGate
|
|
8
8
|
# Multiple private methods allowing StateGate::Builder to generate
|
9
9
|
# attribute setter methods for transition validation.
|
10
10
|
#
|
11
|
-
#
|
12
|
-
# Klass.new(status: :active)
|
11
|
+
# - initializing the attribute with +Class.new+ :
|
12
|
+
# Klass.new(status: :active) #=> ArgumentError
|
13
13
|
#
|
14
|
-
#
|
15
|
-
# Klass.create(status: :active)
|
14
|
+
# - initializing the attribute with +Class.create+ :
|
15
|
+
# Klass.create(status: :active) #=> ArgumentError
|
16
16
|
#
|
17
|
-
#
|
18
|
-
# Klass.create!(status: :active)
|
17
|
+
# - initializing the attribute with <tt>Class.create!</tt> :
|
18
|
+
# Klass.create!(status: :active) #=> ArgumentError
|
19
19
|
#
|
20
|
-
#
|
21
|
-
# .status = :active
|
22
|
-
# .status = :archived
|
20
|
+
# - setting the attribute with +attr=+ :
|
21
|
+
# .status = :active #=> :active
|
22
|
+
# .status = :archived #=> ArgumentError
|
23
23
|
#
|
24
|
-
#
|
25
|
-
# [:status] = :active
|
26
|
-
# [:status] = :archived
|
24
|
+
# - setting the attribute with <tt>[:attr]=</tt> :
|
25
|
+
# [:status] = :active #=> :active
|
26
|
+
# [:status] = :archived #=> ArgumentError
|
27
27
|
#
|
28
|
-
#
|
29
|
-
# .attrubutes = {status: :active}
|
30
|
-
# .attributes = {status: :archived }
|
28
|
+
# - setting the attribute with <tt>attributes=</tt> :
|
29
|
+
# .attrubutes = {status: :active} #=> :active
|
30
|
+
# .attributes = {status: :archived } #=> ArgumentError
|
31
31
|
#
|
32
|
-
#
|
33
|
-
# .assign_attrubutes(status: :active)
|
34
|
-
# .assign_attributes(status: :archived)
|
32
|
+
# - setting the attribute with <tt>assign_attributes</tt> :
|
33
|
+
# .assign_attrubutes(status: :active) #=> :active
|
34
|
+
# .assign_attributes(status: :archived) #=> ArgumentError
|
35
35
|
#
|
36
|
-
#
|
37
|
-
# Klass.update(instance.id, status: :active)
|
38
|
-
# Klass.update(instance.id, status: :archived)
|
36
|
+
# - updating the attribute with <tt>Class.update</tt> :
|
37
|
+
# Klass.update(instance.id, status: :active) #=> :active
|
38
|
+
# Klass.update(instance.id, status: :archived) #=> ArgumentError
|
39
39
|
#
|
40
|
-
#
|
41
|
-
# .update(status: :active)
|
42
|
-
# .update(status: :archived)
|
40
|
+
# - updating the attribute with <tt>.update</tt> :
|
41
|
+
# .update(status: :active) #=> :active
|
42
|
+
# .update(status: :archived) #=> ArgumentError
|
43
43
|
#
|
44
|
-
#
|
45
|
-
# .update_column(:status, :active)
|
46
|
-
# .update_column(:status, :archived)
|
44
|
+
# - updating the attribute with <tt>.update_column</tt> :
|
45
|
+
# .update_column(:status, :active) #=> :active
|
46
|
+
# .update_column(:status, :archived) #=> ArgumentError
|
47
47
|
#
|
48
|
-
#
|
49
|
-
# .update_columns(status: :active)
|
50
|
-
# .update_columns(status: :archived)
|
48
|
+
# - updating the attribute with <tt>.update_columns</tt> :
|
49
|
+
# .update_columns(status: :active) #=> :active
|
50
|
+
# .update_columns(status: :archived) #=> ArgumentError
|
51
51
|
#
|
52
|
-
#
|
53
|
-
# .write_attribute(:status, :active)
|
54
|
-
# .write_attribute(:status, :archived)
|
52
|
+
# - updating the attribute with <tt>.write_attribute</tt> :
|
53
|
+
# .write_attribute(:status, :active) #=> :active
|
54
|
+
# .write_attribute(:status, :archived) #=> ArgumentError
|
55
55
|
#
|
56
56
|
#
|
57
57
|
# === | Forcing a change
|
58
58
|
#
|
59
59
|
# To force a status change that would otherwise be prohibited, preceed the
|
60
60
|
# new state with +force_+ :
|
61
|
-
# .status = :archived
|
62
|
-
# .status = :force_archived
|
61
|
+
# .status = :archived #=> ArgumentError
|
62
|
+
# .status = :force_archived #=> :archived
|
63
63
|
#
|
64
64
|
module TransitionValidationMethods
|
65
65
|
|
@@ -69,19 +69,20 @@ module StateGate
|
|
69
69
|
|
70
70
|
|
71
71
|
|
72
|
+
##
|
72
73
|
# Add prepended instance methods to the klass that catch all methods for
|
73
74
|
# updating the attribute and validated the new value is an allowed transition
|
74
75
|
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
76
|
+
# @note
|
77
|
+
# These methods are only added if the engine has an
|
78
|
+
# include_transition_validations? status on initialisation
|
78
79
|
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
80
|
+
# @note
|
81
|
+
# The three methods "<atrr>=(val)", "write_attribute(<attr>, val)" and
|
82
|
+
# "update_columns(<attr>: val)" cover all the possibilities of setting the
|
83
|
+
# attribute through ActiveRecord.
|
83
84
|
#
|
84
|
-
def
|
85
|
+
def _generate_transition_validation_methods
|
85
86
|
return if @engine.transitionless?
|
86
87
|
|
87
88
|
_prepend__attribute_equals
|
@@ -96,13 +97,14 @@ module StateGate
|
|
96
97
|
# Prepend Module
|
97
98
|
# ======================================================================
|
98
99
|
|
100
|
+
##
|
99
101
|
# Dynamically generated module to hold the validation setter methods
|
100
102
|
# and is pre-pended to the class.
|
101
103
|
#
|
102
104
|
# A new module is create if it doesn't already exist.
|
103
105
|
#
|
104
|
-
#
|
105
|
-
#
|
106
|
+
# @note
|
107
|
+
# the module is named "StateGate::<klass>TranstionValidationMethods"
|
106
108
|
#
|
107
109
|
def _transition_validation_module # rubocop:disable Metrics/MethodLength
|
108
110
|
@_transition_validation_module ||= begin
|
@@ -125,16 +127,18 @@ module StateGate
|
|
125
127
|
# Instance methods
|
126
128
|
# ======================================================================
|
127
129
|
|
130
|
+
##
|
128
131
|
# Adds a method to overwrite the attribute :<attr>=(val) setter, raising an error
|
129
132
|
# if the supplied value is not a valid transition
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
+
#
|
134
|
+
# @example
|
135
|
+
# .status = :archived #=> ArgumentError
|
136
|
+
# .status - :active #=> :active
|
133
137
|
#
|
134
138
|
# ==== actions
|
135
139
|
#
|
136
|
-
#
|
137
|
-
#
|
140
|
+
# - assert it's a valid transition
|
141
|
+
# - call super
|
138
142
|
#
|
139
143
|
def _prepend__attribute_equals
|
140
144
|
attr_name = @attribute
|
@@ -150,18 +154,20 @@ module StateGate
|
|
150
154
|
|
151
155
|
|
152
156
|
|
157
|
+
##
|
153
158
|
# Adds a method to overwrite the instance :write_attribute(attr, val) setter,
|
154
159
|
# raising an error if the supplied value is not a valid transition
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
160
|
+
#
|
161
|
+
# @example
|
162
|
+
# .write_attribute(:status, :archived) #=> ArgumentError
|
163
|
+
# .write_attribute(:status, :active) #=> :active
|
158
164
|
#
|
159
165
|
# ==== actions
|
160
166
|
#
|
161
|
-
#
|
162
|
-
#
|
163
|
-
#
|
164
|
-
#
|
167
|
+
# - loop through each attribute
|
168
|
+
# - get the base attribute name from any alias used
|
169
|
+
# - assert it's a valid transition
|
170
|
+
# - call super
|
165
171
|
#
|
166
172
|
def _prepend__write_attribute
|
167
173
|
return if _transition_validation_module.method_defined?(:write_attribute)
|
@@ -180,18 +186,20 @@ module StateGate
|
|
180
186
|
|
181
187
|
|
182
188
|
|
189
|
+
##
|
183
190
|
# Adds a method to overwrite the instance :update_columns(attr: val) setter,
|
184
191
|
# raising an error if the supplied value is not a valid transition
|
185
|
-
#
|
186
|
-
#
|
187
|
-
#
|
192
|
+
#
|
193
|
+
# @example
|
194
|
+
# .update_columns(status: :archived) #=> ArgumentError
|
195
|
+
# .update_columns(status: :active) #=> :active
|
188
196
|
#
|
189
197
|
# ==== actions
|
190
198
|
#
|
191
|
-
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
199
|
+
# - loop through each attribute
|
200
|
+
# - get the base attribute name from any alias used
|
201
|
+
# - assert it's a valid transition
|
202
|
+
# - call super
|
195
203
|
#
|
196
204
|
def _prepend__update_columns # rubocop:disable Metrics/MethodLength
|
197
205
|
return if _transition_validation_module.method_defined?(:update_columns)
|
@@ -215,10 +223,12 @@ module StateGate
|
|
215
223
|
|
216
224
|
|
217
225
|
|
218
|
-
|
226
|
+
##
|
227
|
+
#Prepends an :itialize method to ensure the attribute is not set on initializing
|
219
228
|
# a new instance unless :forced.
|
220
229
|
#
|
221
|
-
#
|
230
|
+
# @example
|
231
|
+
# Klass.new(status: :archived) #=> ArgumentError
|
222
232
|
#
|
223
233
|
def _prepend__initialize # rubocop:disable Metrics/MethodLength
|
224
234
|
return if _transition_validation_module.method_defined?(:initialize)
|
data/lib/state_gate/builder.rb
CHANGED
@@ -14,11 +14,11 @@ module StateGate
|
|
14
14
|
#
|
15
15
|
# Both Class and Instance methods are generated for:
|
16
16
|
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
17
|
+
# - state interaction
|
18
|
+
# - state sequences
|
19
|
+
# - state scopes
|
20
|
+
# - transition interaction
|
21
|
+
# - transition validation
|
22
22
|
#
|
23
23
|
class Builder
|
24
24
|
|
@@ -35,24 +35,23 @@ module StateGate
|
|
35
35
|
# ======================================================================
|
36
36
|
private
|
37
37
|
|
38
|
+
##
|
38
39
|
# Initialize the Builder, creating the state gate and generating all the
|
39
40
|
# Class and Instace helper methods on the sumbitted klass.
|
40
41
|
#
|
41
|
-
# [
|
42
|
+
# @param [Class] klass
|
42
43
|
# The class containing the attribute to be cast as a state gate
|
43
|
-
# (Class | required)
|
44
44
|
#
|
45
|
-
# [
|
46
|
-
# The name of the
|
47
|
-
# (Symbol | required)
|
45
|
+
# @param [Symbol] attribute_name
|
46
|
+
# The name of the database attribute to use for the state gate
|
48
47
|
#
|
49
|
-
#
|
48
|
+
# @block config
|
50
49
|
# The configuration block for the state gate.
|
51
|
-
# (Block | required)
|
52
50
|
#
|
53
|
-
#
|
51
|
+
# @example
|
52
|
+
# StateGate::Builder.new(Klass, :attribute_name) do
|
54
53
|
# ... configuration ...
|
55
|
-
#
|
54
|
+
# end
|
56
55
|
#
|
57
56
|
def initialize(klass = nil, attribute_name = nil, &config)
|
58
57
|
@klass = klass
|
@@ -70,19 +69,19 @@ module StateGate
|
|
70
69
|
_cast_attribute_as_state_gate
|
71
70
|
|
72
71
|
# generate the helper methods
|
73
|
-
|
72
|
+
_generate_helper_methods
|
74
73
|
end
|
75
74
|
|
76
75
|
|
77
76
|
|
78
77
|
# Generate the helper methods
|
79
78
|
#
|
80
|
-
def
|
79
|
+
def _generate_helper_methods
|
81
80
|
# add the helper methods
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
81
|
+
_generate_scope_methods
|
82
|
+
_generate_state_methods
|
83
|
+
_generate_transition_methods
|
84
|
+
_generate_transition_validation_methods
|
86
85
|
|
87
86
|
# warn if any state gate attribute methods are redefined
|
88
87
|
_generate_method_redefine_detection
|
@@ -90,6 +89,7 @@ module StateGate
|
|
90
89
|
|
91
90
|
|
92
91
|
|
92
|
+
##
|
93
93
|
# Validate the klass to ensure it is a 'Class' and derived from ActiveRecord,
|
94
94
|
# raising an error if not.
|
95
95
|
#
|
@@ -100,15 +100,16 @@ module StateGate
|
|
100
100
|
|
101
101
|
|
102
102
|
|
103
|
+
##
|
103
104
|
# Parse the attribute name to ensure it is a valid input value and
|
104
105
|
# detect if it's an attribute_alias.
|
105
106
|
#
|
106
107
|
# = meta
|
107
108
|
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
109
|
+
# - ensure it exists
|
110
|
+
# - ensure it's a Symbol, to avoid string whitespace issues
|
111
|
+
# - check is it's a registere attribute
|
112
|
+
# - update @attribute & @alias
|
112
113
|
#
|
113
114
|
def _parse_atttribute_name_for_alias
|
114
115
|
if @attribute.nil?
|
@@ -125,6 +126,7 @@ module StateGate
|
|
125
126
|
|
126
127
|
|
127
128
|
|
129
|
+
##
|
128
130
|
# Validate we don't already have a state gate defined for the attribute,
|
129
131
|
# raising an error if not.
|
130
132
|
#
|
@@ -137,13 +139,14 @@ module StateGate
|
|
137
139
|
|
138
140
|
|
139
141
|
|
142
|
+
##
|
140
143
|
# Validate the attribute is a database String attribute,
|
141
144
|
# raising an error if not.
|
142
145
|
#
|
143
146
|
# = meta
|
144
147
|
#
|
145
|
-
#
|
146
|
-
#
|
148
|
+
# - ensure it's mapped to a database column
|
149
|
+
# - ensure it's a :string databse type
|
147
150
|
#
|
148
151
|
def _assert_attribute_name_is_a_database_string_column
|
149
152
|
if @klass.column_names.exclude?(@attribute.to_s)
|
@@ -157,10 +160,12 @@ module StateGate
|
|
157
160
|
|
158
161
|
|
159
162
|
|
163
|
+
##
|
160
164
|
# Builds a StateGate::Engine for the given attribute and add it to
|
161
165
|
# the :stateables repository.
|
162
166
|
#
|
163
|
-
# config
|
167
|
+
# @block config
|
168
|
+
# the user generated configuration for the engine, including states,
|
164
169
|
# transitions and optional settings
|
165
170
|
#
|
166
171
|
def _build_state_gate_engine(&config)
|
@@ -171,6 +176,7 @@ module StateGate
|
|
171
176
|
|
172
177
|
|
173
178
|
|
179
|
+
##
|
174
180
|
# Adds a :stateables class_attribute if it doesn't already exist and
|
175
181
|
# initializes it to an empty Hash.
|
176
182
|
#
|
@@ -178,17 +184,16 @@ module StateGate
|
|
178
184
|
# created when generating state gates. The state gate attribute name is used
|
179
185
|
# as the key.
|
180
186
|
#
|
181
|
-
#
|
182
|
-
# Klass.stateables
|
187
|
+
# @example
|
188
|
+
# Klass.stateables #=> {
|
183
189
|
# status: <StateGate::Engine>,
|
184
190
|
# account: <StateGate::Engine>
|
185
191
|
# }
|
186
192
|
#
|
187
|
-
#
|
188
|
-
#
|
189
|
-
#
|
190
|
-
#
|
191
|
-
# the .class_attribute method.
|
193
|
+
# @note
|
194
|
+
# The default empty Hash is set after the attribute is created to accommodate
|
195
|
+
# ActiveRecord 5.0, even though ActiveRecord 6.0 allows it to be set within
|
196
|
+
# the .class_attribute method.
|
192
197
|
#
|
193
198
|
def _initialize_state_gate_repository
|
194
199
|
return if @klass.methods(false).include?(:stateables)
|
@@ -199,6 +204,7 @@ module StateGate
|
|
199
204
|
|
200
205
|
|
201
206
|
|
207
|
+
##
|
202
208
|
# Builds a StateGate::Type with the custom states for the attribute,
|
203
209
|
# then casts the attribute.
|
204
210
|
#
|
@@ -208,9 +214,9 @@ module StateGate
|
|
208
214
|
#
|
209
215
|
# meta
|
210
216
|
#
|
211
|
-
#
|
212
|
-
#
|
213
|
-
#
|
217
|
+
# - retrieve the root attribute name if the supplied attribute is an alias.
|
218
|
+
# - create a StateGate::Type with attributes states.
|
219
|
+
# - overwrite the attribute, casting the type as the new StateGate::Type
|
214
220
|
#
|
215
221
|
def _cast_attribute_as_state_gate
|
216
222
|
states = @engine.states
|
@@ -220,17 +226,21 @@ module StateGate
|
|
220
226
|
|
221
227
|
|
222
228
|
|
229
|
+
##
|
223
230
|
# Raise an ArgumentError for the given error, using I18n for the message.
|
224
231
|
#
|
225
|
-
#
|
226
|
-
#
|
227
|
-
# args - Hash of attributes to pass to the message string.
|
232
|
+
# @param [Symbol] err
|
233
|
+
# the Symbol key for the I18n message
|
228
234
|
#
|
229
|
-
#
|
230
|
-
#
|
235
|
+
# @param [Hash] args
|
236
|
+
# Hash of attributes to pass to the message string.
|
231
237
|
#
|
232
|
-
#
|
238
|
+
# @option args :klass
|
239
|
+
# When present, args[:klass] will be updated with the 'KlassName'.
|
240
|
+
# @option args :kattr
|
241
|
+
# When true, args[:kattr] will be updated with 'KlassName#attribute'.
|
233
242
|
#
|
243
|
+
# @example
|
234
244
|
# err(:invalid_attribute_type_err, kattr: true)
|
235
245
|
#
|
236
246
|
def err(err, **args)
|