stator 0.4.0 → 0.9.0.beta
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/.github/workflows/build.yml +28 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/Appraisals +23 -0
- data/Gemfile +4 -3
- data/gemfiles/activerecord_5.1.gemfile +12 -0
- data/gemfiles/activerecord_5.1.gemfile.lock +74 -0
- data/gemfiles/activerecord_5.2.gemfile +12 -0
- data/gemfiles/activerecord_5.2.gemfile.lock +74 -0
- data/gemfiles/activerecord_5.gemfile +12 -0
- data/gemfiles/activerecord_5.gemfile.lock +74 -0
- data/gemfiles/activerecord_6.0.gemfile +12 -0
- data/gemfiles/activerecord_6.0.gemfile.lock +74 -0
- data/gemfiles/activerecord_6.1.gemfile +12 -0
- data/gemfiles/activerecord_6.1.gemfile.lock +73 -0
- data/gemfiles/activerecord_7.0.gemfile +12 -0
- data/gemfiles/activerecord_7.0.gemfile.lock +71 -0
- data/lib/stator/alias.rb +51 -30
- data/lib/stator/integration.rb +42 -49
- data/lib/stator/machine.rb +59 -58
- data/lib/stator/model.rb +78 -73
- data/lib/stator/transition.rb +61 -60
- data/lib/stator/version.rb +3 -5
- data/lib/stator.rb +13 -0
- data/spec/model_spec.rb +206 -207
- data/spec/spec_helper.rb +6 -3
- data/spec/support/models.rb +26 -32
- data/spec/support/schema.rb +42 -42
- data/stator.gemspec +1 -0
- metadata +33 -10
- data/.travis.yml +0 -41
- data/gemfiles/ar40.gemfile +0 -10
- data/gemfiles/ar41.gemfile +0 -10
- data/gemfiles/ar42.gemfile +0 -10
- data/gemfiles/ar52.gemfile +0 -10
data/lib/stator/model.rb
CHANGED
@@ -1,113 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Stator
|
2
4
|
module Model
|
5
|
+
extend ActiveSupport::Concern
|
3
6
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
+
included do
|
8
|
+
class_attribute :_stators
|
9
|
+
attr_accessor :_stator_integrations
|
7
10
|
|
8
|
-
|
9
|
-
include TrackerMethods if options[:track] == true
|
11
|
+
validate :_stator_validate_transition
|
10
12
|
|
11
13
|
self._stators ||= {}
|
12
14
|
|
13
|
-
|
14
|
-
f = options[:field] || :state
|
15
|
-
# rescue nil since the table may not exist yet.
|
16
|
-
initial = self.columns_hash[f.to_s].default rescue nil
|
17
|
-
options = options.merge(initial: initial) if initial
|
18
|
-
end
|
19
|
-
|
20
|
-
machine = (self._stators[options[:namespace].to_s] ||= ::Stator::Machine.new(self, options))
|
21
|
-
|
22
|
-
if block_given?
|
23
|
-
machine.instance_eval(&block)
|
24
|
-
machine.evaluate
|
25
|
-
end
|
26
|
-
|
27
|
-
machine
|
15
|
+
before_save :_stator_maybe_track_transition, prepend: true
|
28
16
|
end
|
29
17
|
|
30
|
-
|
31
|
-
|
32
|
-
|
18
|
+
class_methods do
|
19
|
+
def stator(namespace: nil, field: :state, initial: nil, track: true, &block)
|
20
|
+
unless abstract_class?
|
21
|
+
# Discover the default value (usually initial) from the table...
|
22
|
+
# but rescue nil since the table may not exist yet.
|
23
|
+
initial = _determine_initial_stator_state(field)
|
24
|
+
end
|
33
25
|
|
34
|
-
|
26
|
+
opts = { namespace: _stator_namespace(namespace), field: field.to_sym, initial: initial, track: track }
|
35
27
|
|
36
|
-
|
37
|
-
|
38
|
-
before_save :_stator_maybe_track_transition, prepend: true
|
28
|
+
Stator::Machine.find_or_create(self, **opts).tap do |machine|
|
29
|
+
machine.evaluate_dsl(&block) if block_given?
|
39
30
|
end
|
40
31
|
end
|
41
32
|
|
42
|
-
def
|
43
|
-
|
33
|
+
def _stator(namespace)
|
34
|
+
self._stators[_stator_namespace(namespace)]
|
44
35
|
end
|
45
36
|
|
46
|
-
def
|
47
|
-
|
48
|
-
end
|
37
|
+
def _stator_namespace(namespace = nil)
|
38
|
+
namespace = nil if namespace.blank?
|
49
39
|
|
50
|
-
|
51
|
-
_integration(namespace).state_by?(state, t)
|
40
|
+
(namespace || Stator.default_namespace).to_sym
|
52
41
|
end
|
53
42
|
|
54
|
-
|
43
|
+
def _determine_initial_stator_state(field)
|
44
|
+
columns_hash[field.to_s].default.to_sym
|
45
|
+
rescue StandardError
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
end
|
55
49
|
|
56
|
-
|
57
|
-
|
58
|
-
|
50
|
+
def initialize_dup(other)
|
51
|
+
@_stator_integrations = {}
|
52
|
+
super
|
53
|
+
end
|
59
54
|
|
60
|
-
|
61
|
-
|
55
|
+
def without_state_transition_validations(namespace = '')
|
56
|
+
_stator_integration(namespace).without_validation do
|
57
|
+
yield self
|
58
|
+
end
|
59
|
+
end
|
62
60
|
|
63
|
-
|
61
|
+
def without_state_transition_tracking(namespace = '')
|
62
|
+
_stator_integration(namespace).without_transition_tracking do
|
63
|
+
yield self
|
64
64
|
end
|
65
|
+
end
|
65
66
|
|
67
|
+
def current_state
|
68
|
+
_stator_integration.state&.to_sym
|
66
69
|
end
|
67
70
|
|
68
|
-
|
71
|
+
def in_state_at?(state, t, namespace = '')
|
72
|
+
_stator_integration(namespace).in_state_at?(state, t)
|
73
|
+
end
|
69
74
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
74
|
-
end
|
75
|
+
def likely_state_at(t, namespace = '')
|
76
|
+
_stator_integration(namespace).likely_state_at(t)
|
77
|
+
end
|
75
78
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end
|
79
|
+
def state_by?(state, t, namespace = '')
|
80
|
+
_stator_integration(namespace).state_by?(state, t)
|
81
|
+
end
|
80
82
|
|
81
|
-
|
82
|
-
_integration(namespace).without_validation do
|
83
|
-
yield self
|
84
|
-
end
|
85
|
-
end
|
83
|
+
private
|
86
84
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
85
|
+
# core methods
|
86
|
+
def _stator(namespace = nil)
|
87
|
+
self.class._stator(namespace)
|
88
|
+
end
|
92
89
|
|
93
|
-
|
90
|
+
def _stator_namespace(namespace = nil)
|
91
|
+
self.class._stator_namespace(namespace)
|
92
|
+
end
|
94
93
|
|
95
|
-
|
96
|
-
|
97
|
-
_integration(namespace).validate_transition
|
98
|
-
end
|
99
|
-
end
|
94
|
+
def _stator_integration(namespace = nil)
|
95
|
+
ns = _stator_namespace(namespace)
|
100
96
|
|
101
|
-
|
102
|
-
|
97
|
+
self._stator_integrations ||= {}
|
98
|
+
self._stator_integrations[ns] ||= self.class._stator(ns).integration(self)
|
99
|
+
end
|
100
|
+
|
101
|
+
# validation/transitional
|
102
|
+
def _stator_validate_transition
|
103
|
+
self._stators.each_key do |namespace|
|
104
|
+
_stator_integration(namespace).validate_transition
|
103
105
|
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def _stator_maybe_track_transition
|
109
|
+
self._stators.each do |namespace, machine|
|
110
|
+
next unless machine.tracking_enabled?
|
104
111
|
|
105
|
-
|
106
|
-
@_integrations ||= {}
|
107
|
-
@_integrations[namespace] ||= _stator(namespace).integration(self)
|
108
|
-
@_integrations[namespace]
|
112
|
+
_stator_integration(namespace).track_transition
|
109
113
|
end
|
110
114
|
|
115
|
+
true
|
111
116
|
end
|
112
117
|
end
|
113
118
|
end
|
data/lib/stator/transition.rb
CHANGED
@@ -1,44 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Stator
|
2
4
|
class Transition
|
3
|
-
|
4
|
-
ANY = '__any__'
|
5
|
-
|
6
|
-
attr_reader :name
|
7
|
-
attr_reader :full_name
|
5
|
+
attr_reader :namespace, :name, :attr_name, :from_states, :to_state, :class_name, :callbacks
|
8
6
|
|
9
7
|
def initialize(class_name, name, namespace = nil)
|
10
|
-
@class_name
|
11
|
-
@name
|
12
|
-
@namespace
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@callbacks = {}
|
8
|
+
@class_name = class_name
|
9
|
+
@name = name&.to_sym
|
10
|
+
@namespace = namespace&.to_sym
|
11
|
+
@from_states = []
|
12
|
+
@to_state = nil
|
13
|
+
@callbacks = {}
|
17
14
|
end
|
18
15
|
|
19
|
-
def
|
20
|
-
@
|
16
|
+
def attr_name
|
17
|
+
@attr_name ||= generate_attr_name
|
21
18
|
end
|
22
19
|
|
23
|
-
def
|
24
|
-
@
|
20
|
+
def from_states(*new_froms)
|
21
|
+
@from_states |= new_froms
|
25
22
|
end
|
23
|
+
alias from from_states
|
26
24
|
|
27
|
-
def
|
28
|
-
@
|
29
|
-
end
|
30
|
-
|
31
|
-
def from_states
|
32
|
-
@froms
|
25
|
+
def to(new_to)
|
26
|
+
@to_state = new_to
|
33
27
|
end
|
34
28
|
|
35
29
|
def can?(current_state)
|
36
|
-
|
30
|
+
from_states.include?(current_state) || from_states.include?(Stator::ANY) || current_state == Stator::ANY
|
37
31
|
end
|
38
32
|
|
39
|
-
def valid?(
|
40
|
-
|
41
|
-
|
33
|
+
def valid?(from_check, to_check)
|
34
|
+
from_check = from_check&.to_sym # coming from the database, i suspect
|
35
|
+
|
36
|
+
can?(from_check) && (to_check == to_state || to_check == ANY || to_state == ANY)
|
42
37
|
end
|
43
38
|
|
44
39
|
def conditional(options = {}, &block)
|
@@ -46,79 +41,85 @@ module Stator
|
|
46
41
|
end
|
47
42
|
|
48
43
|
def any
|
49
|
-
ANY
|
44
|
+
Stator::ANY
|
50
45
|
end
|
51
46
|
|
52
47
|
def evaluate
|
53
|
-
generate_methods
|
48
|
+
generate_methods if attr_name.present?
|
54
49
|
end
|
55
50
|
|
56
|
-
|
51
|
+
private
|
57
52
|
|
58
53
|
def klass
|
59
|
-
|
54
|
+
class_name.constantize
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate_attr_name
|
58
|
+
if namespace == Stator.default_namespace
|
59
|
+
name
|
60
|
+
else
|
61
|
+
[namespace, name].compact.join('_').to_sym
|
62
|
+
end
|
60
63
|
end
|
61
64
|
|
62
65
|
def callbacks(kind)
|
63
|
-
|
66
|
+
callbacks[kind] || []
|
64
67
|
end
|
65
68
|
|
66
69
|
def conditional_block(options = {})
|
67
70
|
options[:use_previous] ||= false
|
68
71
|
|
69
|
-
_namespace =
|
70
|
-
_froms =
|
71
|
-
_to =
|
72
|
-
|
73
|
-
|
74
|
-
(
|
75
|
-
|
76
|
-
) &&
|
77
|
-
_froms.include?(
|
78
|
-
_froms.include?(
|
79
|
-
|
80
|
-
self._stator(_namespace).integration(self).state == _to ||
|
81
|
-
_to == ::Stator::Transition::ANY
|
82
|
-
)
|
72
|
+
_namespace = namespace
|
73
|
+
_froms = from_states
|
74
|
+
_to = to_state
|
75
|
+
|
76
|
+
proc do
|
77
|
+
integration = self.class._stator(_namespace).integration(self)
|
78
|
+
|
79
|
+
integration.state_changed?(options[:use_previous]) &&
|
80
|
+
_froms.include?(integration.state_was(options[:use_previous])) ||
|
81
|
+
_froms.include?(Stator::ANY) &&
|
82
|
+
integration.state == _to || _to == Stator::ANY
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
86
|
def generate_methods
|
87
87
|
klass.class_eval <<-EV, __FILE__, __LINE__ + 1
|
88
|
-
def #{
|
89
|
-
integration =
|
88
|
+
def #{attr_name}(should_save = true)
|
89
|
+
integration = _stator_integration(:#{namespace})
|
90
90
|
|
91
|
-
unless can_#{
|
92
|
-
integration.invalid_transition!(integration.state,
|
91
|
+
unless can_#{attr_name}?
|
92
|
+
integration.invalid_transition!(integration.state, :#{to_state}) if should_save
|
93
93
|
return false
|
94
94
|
end
|
95
95
|
|
96
|
-
integration.state =
|
96
|
+
integration.state = :#{to_state}
|
97
|
+
|
97
98
|
self.save if should_save
|
98
99
|
end
|
99
100
|
|
100
|
-
def #{
|
101
|
-
integration =
|
101
|
+
def #{attr_name}!
|
102
|
+
integration = _stator_integration(:#{namespace})
|
102
103
|
|
103
|
-
unless can_#{
|
104
|
-
integration.invalid_transition!(integration.state,
|
104
|
+
unless can_#{attr_name}?
|
105
|
+
integration.invalid_transition!(integration.state, :#{to_state})
|
105
106
|
raise ActiveRecord::RecordInvalid.new(self)
|
106
107
|
end
|
107
108
|
|
108
|
-
integration.state =
|
109
|
+
integration.state = :#{to_state}
|
109
110
|
self.save!
|
110
111
|
end
|
111
112
|
|
112
|
-
def can_#{
|
113
|
-
integration =
|
113
|
+
def can_#{attr_name}?
|
114
|
+
integration = _stator_integration(:#{namespace})
|
114
115
|
return true if integration.skip_validations
|
115
116
|
|
116
|
-
machine = self._stator(
|
117
|
-
transition = machine.transitions.detect{|t| t.
|
117
|
+
machine = self._stator(:#{namespace})
|
118
|
+
transition = machine.transitions.detect { |t| t.attr_name == :#{attr_name} }
|
119
|
+
|
118
120
|
transition.can?(integration.state)
|
119
121
|
end
|
120
122
|
EV
|
121
123
|
end
|
122
|
-
|
123
124
|
end
|
124
125
|
end
|
data/lib/stator/version.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Stator
|
4
|
-
|
5
4
|
MAJOR = 0
|
6
|
-
MINOR =
|
5
|
+
MINOR = 9
|
7
6
|
PATCH = 0
|
8
|
-
PRERELEASE =
|
9
|
-
|
10
|
-
VERSION = [MAJOR, MINOR, PATCH, PRERELEASE].compact.join(".")
|
7
|
+
PRERELEASE = "beta"
|
11
8
|
|
9
|
+
VERSION = [MAJOR, MINOR, PATCH, PRERELEASE].compact.join('.')
|
12
10
|
end
|
data/lib/stator.rb
CHANGED
@@ -1,6 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'stator/version'
|
2
4
|
require 'stator/alias'
|
3
5
|
require 'stator/integration'
|
4
6
|
require 'stator/machine'
|
5
7
|
require 'stator/model'
|
6
8
|
require 'stator/transition'
|
9
|
+
|
10
|
+
require 'active_support/concern'
|
11
|
+
require 'debug'
|
12
|
+
|
13
|
+
module Stator
|
14
|
+
ANY = :__ANY__
|
15
|
+
|
16
|
+
def self.default_namespace
|
17
|
+
ENV.fetch('STATOR_NAMESPACE', :default).to_sym
|
18
|
+
end
|
19
|
+
end
|