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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c914825dceae121cfabc62d93c48e952d0d1ae4995bc0e477d1e2aa2e0cf9107
|
4
|
+
data.tar.gz: e49f5d7fd2517d88a7ae8c225baa452c75aa6cba94828e2e182fcb3462ffccec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1042ce00609a2501647803fce1a41b689c45084726011c6f0f36e2498b9ca48110cf7d99160311eed74204f939255332be79894056098ccf2c1883dd88d0e6da
|
7
|
+
data.tar.gz: ba89c3bd3d36181c109ab56d5d9a6d8f68f735bfc645075d3dd16f2e8c7f829ff82746ef1db093f76743d05532159b864e75f78a4e960389d22c8ad818d76f84
|
@@ -14,54 +14,73 @@ module StateGate
|
|
14
14
|
# ======================================================================
|
15
15
|
private
|
16
16
|
|
17
|
+
##
|
17
18
|
# Check if a class method is already defined. Checks are made:
|
18
19
|
# 1 --> is it an ActiveRecord dangerous method?
|
19
20
|
# 2 --> is it an ActiveRecord defined class method?
|
20
21
|
# 3 --> is it a singleton method of the klass?
|
21
22
|
# 4 --> is it defined within any of the klass ancestors?
|
22
23
|
#
|
23
|
-
#
|
24
|
+
# @param [Symbol] method_name
|
25
|
+
# the class method name to test for conclict
|
24
26
|
#
|
25
|
-
|
27
|
+
# @raise [ConflictError]
|
28
|
+
# if the method name has already been defined
|
29
|
+
#
|
30
|
+
def _detect_class_method_conflict!(method_name)
|
26
31
|
defining_klass = _active_record_protected_method?(method_name) ||
|
27
32
|
_klass_singleton_method?(method_name) ||
|
28
33
|
_klass_ancestor_singleton_method?(method_name)
|
29
34
|
|
30
35
|
return unless defining_klass
|
31
36
|
|
32
|
-
|
37
|
+
_raise_conflict_error method_name, type: 'a class', source: defining_klass
|
33
38
|
end
|
34
39
|
|
35
40
|
|
36
41
|
|
42
|
+
##
|
37
43
|
# Check an instance method is already defined. Checks are made:
|
38
44
|
# 1 --> is it an ActiveRecord dangerous method?
|
39
45
|
# 2 --> is it an ActiveRecord defined instance method?
|
40
46
|
# 3 --> is it an instance method of the klass?
|
41
47
|
# 4 --> is it defined within any of the klass ancestors?
|
42
48
|
#
|
43
|
-
#
|
49
|
+
# @param [Symbol] method_name
|
50
|
+
# the instance method name to test for conclict
|
51
|
+
#
|
52
|
+
# @raise [ConflictError]
|
53
|
+
# if the method name has already been defined
|
44
54
|
#
|
45
|
-
def
|
55
|
+
def _detect_instance_method_conflict!(method_name)
|
46
56
|
defining_klass = _active_record_protected_method?(method_name) ||
|
47
57
|
_klass_instance_method?(method_name) ||
|
48
58
|
_klass_ancestor_instance_method?(method_name)
|
49
59
|
|
50
60
|
return unless defining_klass
|
51
61
|
|
52
|
-
|
62
|
+
_raise_conflict_error method_name, source: defining_klass
|
53
63
|
end
|
54
64
|
|
55
65
|
|
56
66
|
|
57
67
|
# Raise a StateGate::ConflictError with a details message of the problem
|
58
68
|
#
|
59
|
-
#
|
69
|
+
# @param [Symbol] method_name
|
70
|
+
# the method name
|
71
|
+
#
|
72
|
+
# @param [String] type
|
73
|
+
# the optional definition of which type of errors this is: instance or class
|
60
74
|
#
|
61
|
-
#
|
62
|
-
#
|
75
|
+
# @param [String] source
|
76
|
+
# the optional class name
|
63
77
|
#
|
64
|
-
|
78
|
+
# @raise [ConflictError]
|
79
|
+
# with the message:
|
80
|
+
# StateGate for Klass#attribute will generate a class
|
81
|
+
# method 'statuses', which is already defined by ActiveRecord.
|
82
|
+
#
|
83
|
+
def _raise_conflict_error(method_name, type: 'an instance', source: 'ActiveRecord')
|
65
84
|
fail StateGate::ConflictError, I18n.t('state_gate.builder.conflict_err',
|
66
85
|
klass: @klass,
|
67
86
|
attribute: @attribute,
|
@@ -72,25 +91,37 @@ module StateGate
|
|
72
91
|
|
73
92
|
|
74
93
|
|
94
|
+
##
|
75
95
|
# Check if the method is an ActiveRecord dangerous method name
|
76
96
|
#
|
77
|
-
|
97
|
+
# @param [Symbol] method_name
|
98
|
+
# the method name
|
99
|
+
#
|
100
|
+
def _active_record_protected_method?(method_name)
|
78
101
|
'ActiveRecord' if _dangerous_method_names.include?(method_name)
|
79
102
|
end
|
80
103
|
|
81
104
|
|
82
105
|
|
106
|
+
##
|
83
107
|
# Check if the method is a singleton method of the klass
|
84
108
|
#
|
85
|
-
|
109
|
+
# @param [Symbol] method_name
|
110
|
+
# the method name
|
111
|
+
#
|
112
|
+
def _klass_singleton_method?(method_name)
|
86
113
|
@klass.name if @klass.singleton_methods(false).include?(method_name.to_sym)
|
87
114
|
end
|
88
115
|
|
89
116
|
|
90
117
|
|
118
|
+
##
|
91
119
|
# Check if the method is an ancestral singleton method of the klass
|
92
120
|
#
|
93
|
-
|
121
|
+
# @param [Symbol] method_name
|
122
|
+
# the method name
|
123
|
+
#
|
124
|
+
def _klass_ancestor_singleton_method?(method_name)
|
94
125
|
return nil unless @klass.respond_to?(method_name)
|
95
126
|
|
96
127
|
@klass.singleton_class
|
@@ -101,17 +132,25 @@ module StateGate
|
|
101
132
|
|
102
133
|
|
103
134
|
|
135
|
+
##
|
104
136
|
# Check if the method an instance method of the klass
|
105
137
|
#
|
106
|
-
|
138
|
+
# @param [Symbol] method_name
|
139
|
+
# the method name
|
140
|
+
#
|
141
|
+
def _klass_instance_method?(method_name)
|
107
142
|
@klass.instance_methods(false).include?(method_name.to_sym) ? @klass.name : nil
|
108
143
|
end
|
109
144
|
|
110
145
|
|
111
146
|
|
147
|
+
##
|
112
148
|
# Check if the method is an ancestral singleton method of the klass
|
113
149
|
#
|
114
|
-
|
150
|
+
# @param [Symbol] method_name
|
151
|
+
# the method name
|
152
|
+
#
|
153
|
+
def _klass_ancestor_instance_method?(method_name)
|
115
154
|
return nil unless @klass.instance_methods.include?(method_name.to_sym)
|
116
155
|
|
117
156
|
@klass.ancestors
|
@@ -121,9 +160,11 @@ module StateGate
|
|
121
160
|
|
122
161
|
|
123
162
|
|
124
|
-
|
125
|
-
#
|
126
|
-
#
|
163
|
+
##
|
164
|
+
# @return [Array[Symbol]]
|
165
|
+
# array of dagerous methods names found in
|
166
|
+
# ActiveRecord::AttributeMethods::RESTRICTED_CLASS_METHODS (which is called
|
167
|
+
# BLACKLISTED_CLASS_METHODS in 5.0 and 5.1)
|
127
168
|
#
|
128
169
|
def _dangerous_method_names
|
129
170
|
%w[private public protected allocate new name parent superclass]
|
@@ -19,14 +19,15 @@ module StateGate
|
|
19
19
|
# = Dynamic Module Creation
|
20
20
|
# ======================================================================
|
21
21
|
|
22
|
+
##
|
22
23
|
# Dynamically generated module to hold the StateGate helper methods. This
|
23
24
|
# keeps a clear distinction between the state machine helper methods and the klass'
|
24
25
|
# own methods.
|
25
26
|
#
|
26
27
|
# The module is named after the class and is created if needed, or reused if exisitng.
|
27
28
|
#
|
28
|
-
#
|
29
|
-
#
|
29
|
+
# @note
|
30
|
+
# the module is named "<klass>::StateGate_HelperMethods"
|
30
31
|
#
|
31
32
|
def _helper_methods_module
|
32
33
|
@_helper_methods_module ||= begin
|
@@ -46,6 +47,7 @@ module StateGate
|
|
46
47
|
# ======================================================================
|
47
48
|
|
48
49
|
|
50
|
+
##
|
49
51
|
# Adds the hook method :method_added to the Klass, detecting any new method
|
50
52
|
# definitions for an attribute already defined as a StateGate.
|
51
53
|
#
|
@@ -54,16 +56,15 @@ module StateGate
|
|
54
56
|
#
|
55
57
|
# method_name - the name of the newly defined method.
|
56
58
|
#
|
57
|
-
#
|
59
|
+
# @note
|
60
|
+
# This method is added last so it does not trigger when StateGate adds
|
61
|
+
# the attribute methods.
|
58
62
|
#
|
59
|
-
#
|
60
|
-
# the attribute methods.
|
63
|
+
# meta
|
61
64
|
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
# * does the new defined method use 'attr' or 'attr='?
|
66
|
-
# * if so then record an error logger if denied, othewise use `puts`
|
65
|
+
# - loop though each state machine attribute.
|
66
|
+
# - does the new defined method use 'attr' or 'attr='?
|
67
|
+
# - if so then record an error logger if denied, othewise use `puts`
|
67
68
|
#
|
68
69
|
def _generate_method_redefine_detection # rubocop:disable Metrics/MethodLength
|
69
70
|
@klass.instance_eval(%(
|
@@ -89,29 +90,42 @@ module StateGate
|
|
89
90
|
# Method Creation
|
90
91
|
# ======================================================================
|
91
92
|
|
93
|
+
##
|
92
94
|
# Add an Class helper method to the _helper_methods_module
|
93
95
|
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
96
|
+
# @param [String] method_name
|
97
|
+
# name for the method to check for conflicts
|
98
|
+
#
|
99
|
+
# @param [String] file
|
100
|
+
# file name for error reporting
|
101
|
+
#
|
102
|
+
# @param [String,Integer] line
|
103
|
+
# line number for error reporting
|
104
|
+
#
|
105
|
+
# @param [String] method_body
|
106
|
+
# a String to be evaluated in the module
|
98
107
|
#
|
99
|
-
def
|
100
|
-
|
108
|
+
def _add__klass__helper_method(method_name, file, line, method_body)
|
109
|
+
_detect_class_method_conflict!(method_name)
|
101
110
|
@klass.instance_eval(method_body, file, line)
|
102
111
|
end
|
103
112
|
|
104
113
|
|
105
114
|
|
115
|
+
##
|
106
116
|
# Add an instance helper method to the _helper_methods_module
|
107
117
|
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
118
|
+
# @param [String] method_name
|
119
|
+
# name for the method to check for conflicts
|
120
|
+
# @param [String] file
|
121
|
+
# file name for error reporting
|
122
|
+
# @param [String] line
|
123
|
+
# line number for error reporting
|
124
|
+
# @param [String] method_body
|
125
|
+
# a String to bhe evaluates in the module
|
112
126
|
#
|
113
|
-
def
|
114
|
-
|
127
|
+
def _add__instance__helper_method(method_name, file, line, method_body)
|
128
|
+
_detect_instance_method_conflict!(method_name)
|
115
129
|
_helper_methods_module.module_eval(method_body, file, line)
|
116
130
|
end
|
117
131
|
|
@@ -8,14 +8,14 @@ module StateGate
|
|
8
8
|
# Multiple private methods enabling StateGate::Builder to generate
|
9
9
|
# scopes for each state.
|
10
10
|
#
|
11
|
-
#
|
12
|
-
# Klass.active
|
11
|
+
# - fetch all records with the given state:
|
12
|
+
# Klass.active #=> Klass.where(state: :active)
|
13
13
|
#
|
14
|
-
#
|
15
|
-
# Klass.not_active
|
14
|
+
# - fetch all records without the given state:
|
15
|
+
# Klass.not_active #=> Klass.where.not(state: :active)
|
16
16
|
#
|
17
|
-
#
|
18
|
-
# Klass.with_statuses(:pending, :active)
|
17
|
+
# - fetch all records with the supplied states:
|
18
|
+
# Klass.with_statuses(:pending, :active) #=> Klass.where(state: [:pending, :active])
|
19
19
|
#
|
20
20
|
module ScopeMethods
|
21
21
|
|
@@ -24,12 +24,13 @@ module StateGate
|
|
24
24
|
private
|
25
25
|
|
26
26
|
|
27
|
+
##
|
27
28
|
# Add scopes to the klass for filtering by state
|
28
29
|
#
|
29
|
-
#
|
30
|
-
#
|
30
|
+
# @note
|
31
|
+
# The scope name is a concatenation of <prefix><state name><suffix>
|
31
32
|
#
|
32
|
-
def
|
33
|
+
def _generate_scope_methods
|
33
34
|
return unless @engine.include_scopes?
|
34
35
|
|
35
36
|
_add__klass__state_scopes
|
@@ -45,49 +46,58 @@ module StateGate
|
|
45
46
|
# Klass methods
|
46
47
|
# ======================================================================
|
47
48
|
|
49
|
+
##
|
48
50
|
# Add a klass method that scopes records to the specified state.
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# Klass.active #=> ActiveRecord::Relation
|
54
|
+
# Klass.active_status #=> ActiveRecord::Relation
|
52
55
|
#
|
53
56
|
def _add__klass__state_scopes
|
54
57
|
attr_name = @attribute
|
55
58
|
|
56
59
|
@engine.states.each do |state|
|
57
60
|
scope_name = @engine.scope_name_for_state(state)
|
58
|
-
|
61
|
+
_detect_class_method_conflict! scope_name
|
59
62
|
@klass.scope(scope_name, -> { where(attr_name => state) })
|
60
63
|
end # each state
|
61
64
|
end # _add__klass__state_scopes
|
62
65
|
|
63
66
|
|
64
67
|
|
68
|
+
##
|
65
69
|
# Add a klass method that scopes records to those without the specified state.
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# Klass.not_active #=> ActiveRecord::Relation
|
73
|
+
# Klass.not_active_status #=> ActiveRecord::Relation
|
69
74
|
#
|
70
75
|
def _add__klass__not_state_scopes
|
71
76
|
attr_name = @attribute
|
72
77
|
|
73
78
|
@engine.states.each do |state|
|
74
79
|
scope_name = @engine.scope_name_for_state(state)
|
75
|
-
|
80
|
+
_detect_class_method_conflict! "not_#{scope_name}"
|
76
81
|
@klass.scope "not_#{scope_name}", -> { where.not(attr_name => state) }
|
77
82
|
end # each state
|
78
83
|
end # _add__klass__not_state_scopes
|
79
84
|
|
80
85
|
|
81
86
|
|
87
|
+
##
|
82
88
|
# Add a klass method that scopes records to the given states.
|
83
|
-
#
|
84
|
-
#
|
89
|
+
#
|
90
|
+
# @param [Symbol] method_name
|
91
|
+
# the method name for the new scope
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# Klass.with_statuses(:active, :pending) #=> ActiveRecord::Relation
|
85
95
|
#
|
86
96
|
def _add__klass__with_attrs_scope(method_name = @attribute)
|
87
97
|
attr_name = @attribute
|
88
98
|
method_name = "with_#{method_name.to_s.pluralize}"
|
89
99
|
|
90
|
-
|
100
|
+
_detect_class_method_conflict! method_name
|
91
101
|
@klass.scope method_name, ->(states) { where(attr_name => Array(states)) }
|
92
102
|
end # _add__klass__with_attrs_scope
|
93
103
|
|