stateful_model_rails 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StatefulModelRails::Transition
4
+ attr_reader :from, :to
5
+
6
+ def initialize(from, to)
7
+ @from = from
8
+ @to = to
9
+ end
10
+
11
+ def ==(other)
12
+ from == other.from && to == other.to
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StatefulModelRails
4
+ VERSION = "0.2.0"
5
+ 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: []