stateful_model_rails 0.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 +7 -0
- data/lib/stateful_model_rails/state_machine.rb +166 -0
- data/lib/stateful_model_rails/transition.rb +14 -0
- data/lib/stateful_model_rails/version.rb +5 -0
- data/lib/stateful_model_rails.rb +42 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 175a956c79dfaf1d4eac4f48154369a2c36467e810cb75a0674f330184429fa3
|
4
|
+
data.tar.gz: fe3c88e95de8cad9e9313e335c131296f923780b787b7f34bcbeff7121309af6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 37944551a657d3e516219dd6e97b16f6e684b99fc444324315f2c25cfe9c5152e5dafe03ca1595fc396113fec5646fe5c2c2e5e7fc020b2a13496519b5510669
|
7
|
+
data.tar.gz: a5cb4f7100d943cb29ec393c4a9465c72d0dc09010988ca1a170553bb0f743605c170dd275af5151ef410787cd0ec0b66c0996966e1f69828e5002e77f79b9b4
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StatefulModelRails::StateMachine
|
4
|
+
class State
|
5
|
+
class << self
|
6
|
+
attr_reader :before_leave_callbacks, :after_leave_callbacks, :before_enter_callbacks, :after_enter_callbacks
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :parent
|
10
|
+
|
11
|
+
def initialize(parent)
|
12
|
+
@parent = parent
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.before_leave(&block)
|
16
|
+
@before_leave_callbacks ||= []
|
17
|
+
@before_leave_callbacks << block
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.after_leave(&block)
|
21
|
+
@after_leave_callbacks ||= []
|
22
|
+
@after_leave_callbacks << block
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.before_enter(&block)
|
26
|
+
@before_enter_callbacks ||= []
|
27
|
+
@before_enter_callbacks << block
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.after_enter(&block)
|
31
|
+
@after_enter_callbacks ||= []
|
32
|
+
@after_enter_callbacks << block
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_before_leave
|
36
|
+
return unless self.class.before_leave_callbacks
|
37
|
+
|
38
|
+
self.class.before_leave_callbacks.each { |b| parent.instance_exec(self, &b) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_after_leave
|
42
|
+
return unless self.class.after_leave_callbacks
|
43
|
+
|
44
|
+
self.class.after_leave_callbacks.each { |b| parent.instance_exec(self, &b) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def run_before_enter
|
48
|
+
return unless self.class.before_enter_callbacks
|
49
|
+
|
50
|
+
self.class.before_enter_callbacks.each { |b| parent.instance_exec(self, &b) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def run_after_enter
|
54
|
+
return unless self.class.after_enter_callbacks
|
55
|
+
|
56
|
+
self.class.after_enter_callbacks.each { |b| parent.instance_exec(self, &b) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def name
|
60
|
+
self.class.name
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class StateMachineInternal
|
65
|
+
attr_reader :seen_states, :transition_map, :transition_callback, :field_name
|
66
|
+
|
67
|
+
def initialize(field_name)
|
68
|
+
@field_name = field_name
|
69
|
+
@seen_states = []
|
70
|
+
@transition_map = {}
|
71
|
+
@transition_callback = nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def transition(event, from:, to:)
|
75
|
+
@transition_map[event] ||= []
|
76
|
+
Array(from).each do |from|
|
77
|
+
@transition_map[event] << StatefulModelRails::Transition.new(from, to)
|
78
|
+
|
79
|
+
@seen_states << from
|
80
|
+
end
|
81
|
+
|
82
|
+
@seen_states << to
|
83
|
+
@seen_states.uniq!
|
84
|
+
end
|
85
|
+
|
86
|
+
def record_transition_with(&block)
|
87
|
+
@transition_callback = block
|
88
|
+
end
|
89
|
+
|
90
|
+
def install_event_helpers!(base)
|
91
|
+
events = @transition_map.keys
|
92
|
+
|
93
|
+
events.each do |event|
|
94
|
+
fromtos = @transition_map[event]
|
95
|
+
|
96
|
+
base.instance_eval do
|
97
|
+
define_method(event) do |**kwargs|
|
98
|
+
with_lock do
|
99
|
+
matching_froms = fromtos.select { |fr| fr.from == state.class }
|
100
|
+
|
101
|
+
raise TooManyDestinationStates if matching_froms.length > 1
|
102
|
+
raise StatefulModelRails::NoMatchingTransition.new(state.name, event.to_s) if matching_froms.empty?
|
103
|
+
|
104
|
+
matching_from = matching_froms[0]
|
105
|
+
|
106
|
+
from_state = matching_from.from.new(self)
|
107
|
+
to_state = matching_from.to.new(self)
|
108
|
+
|
109
|
+
from_state.run_before_leave if from_state.respond_to?(:run_before_leave)
|
110
|
+
to_state.run_before_enter if to_state.respond_to?(:run_before_enter)
|
111
|
+
|
112
|
+
update!(self.class.state_machine_instance.field_name.to_sym => matching_from.to.name)
|
113
|
+
self.class.state_machine_instance.transition_callback&.call(matching_from.from.name, matching_from.to.name, **kwargs)
|
114
|
+
|
115
|
+
from_state.run_after_leave if from_state.respond_to?(:run_after_leave)
|
116
|
+
to_state.run_after_enter if to_state.respond_to?(:run_after_enter)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.included(base)
|
125
|
+
base.define_singleton_method(
|
126
|
+
:state_machine,
|
127
|
+
method(:included__state_machine)
|
128
|
+
)
|
129
|
+
|
130
|
+
base.define_method(
|
131
|
+
:state,
|
132
|
+
method(:included__state)
|
133
|
+
)
|
134
|
+
|
135
|
+
base.define_singleton_method(
|
136
|
+
:state_machine_instance,
|
137
|
+
method(:included__state_machine_instance)
|
138
|
+
)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def included__state_machine(opts = {}, &)
|
143
|
+
field_name = opts.fetch(:on, "state").to_s
|
144
|
+
|
145
|
+
@state_machine = StatefulModelRails::StateMachine::StateMachineInternal.new(field_name)
|
146
|
+
@state_machine.instance_eval(&)
|
147
|
+
@state_machine.install_event_helpers!(self)
|
148
|
+
@state_machine
|
149
|
+
end
|
150
|
+
|
151
|
+
def included__state
|
152
|
+
sm_instance = self.class.state_machine_instance
|
153
|
+
field_name = sm_instance.field_name
|
154
|
+
|
155
|
+
st = sm_instance.seen_states.detect do |sf|
|
156
|
+
sf.name.underscore == attributes[field_name].underscore
|
157
|
+
end
|
158
|
+
|
159
|
+
raise StatefulModelRails::MissingStateDefinition, attributes[field_name] if st.nil?
|
160
|
+
|
161
|
+
st.new(self)
|
162
|
+
end
|
163
|
+
|
164
|
+
def included__state_machine_instance
|
165
|
+
@state_machine
|
166
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stateful_model_rails/version"
|
4
|
+
require "stateful_model_rails/state_machine"
|
5
|
+
require "stateful_model_rails/transition"
|
6
|
+
|
7
|
+
require "active_support/core_ext/string/inflections"
|
8
|
+
|
9
|
+
module StatefulModelRails
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
class MissingStateDefinition < Error
|
13
|
+
def initialize(missing_class_name)
|
14
|
+
@missing_class_name = missing_class_name
|
15
|
+
|
16
|
+
super()
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"Couldn't find class definition for state named #{@missing_class_name}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class NoMatchingTransition < Error
|
25
|
+
def initialize(current_state, triggering_event)
|
26
|
+
@current_state = current_state
|
27
|
+
@triggering_event = triggering_event
|
28
|
+
|
29
|
+
super()
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
"There is no event #{@triggering_event} from #{@current_state}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class DirtyModel < Error
|
38
|
+
def to_s
|
39
|
+
"There are unsaved changes to this record that would be override by doing a state machine transition"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stateful_model_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Anderson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-08-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.2.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.2.0
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- benanderson@acidic.co.nz
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- lib/stateful_model_rails.rb
|
49
|
+
- lib/stateful_model_rails/state_machine.rb
|
50
|
+
- lib/stateful_model_rails/transition.rb
|
51
|
+
- lib/stateful_model_rails/version.rb
|
52
|
+
homepage: https://github.com/bagedevimo/stateful-model-rails
|
53
|
+
licenses:
|
54
|
+
- MIT
|
55
|
+
metadata:
|
56
|
+
homepage_uri: https://github.com/bagedevimo/stateful-model-rails
|
57
|
+
source_code_uri: https://github.com/bagedevimo/stateful-model-rails
|
58
|
+
changelog_uri: https://github.com/bagedevimo/stateful-model-rails
|
59
|
+
rubygems_mfa_required: 'true'
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.1.0
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubygems_version: 3.3.26
|
76
|
+
signing_key:
|
77
|
+
specification_version: 4
|
78
|
+
summary: A tiny library for making ActiveRecord models into state machines
|
79
|
+
test_files: []
|