circulator 1.0.0 → 2.0.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/CHANGELOG.md +3 -3
- data/README.md +5 -5
- data/lib/circulator/flow.rb +1 -3
- data/lib/circulator/version.rb +1 -1
- data/lib/circulator.rb +239 -1
- metadata +1 -2
- data/lib/circulator/diverter.rb +0 -242
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0a33536db417f338113b2ef4a5656a38994652f4454f8583ea8e22d626a0e9e
|
4
|
+
data.tar.gz: 394085a68c1a7444192264fa69152be2827663284f4221b844f4fbfcb45cf64b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4489ca3bf464fe0b37ad3b2e41096fcce05a2d5bcc5348d25e7750f278c666fd10c266e397635846b0093d1ace1064da754d50a0d6973b07f2a4086b5983926d
|
7
|
+
data.tar.gz: 4da4067d6b07dccc9207e17c6095b236dab9f9e7c1d6399f5ff0c475b761b84da34490ec5d8d61ab71678e18fd014b327f6f9358b5aa571b8c70e19306698da6
|
data/CHANGELOG.md
CHANGED
@@ -5,8 +5,8 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
-
## [
|
8
|
+
## [2.0.0] - 2025-09-12
|
9
9
|
|
10
|
-
###
|
10
|
+
### Removed
|
11
11
|
|
12
|
-
-
|
12
|
+
- The `Circulator::Diverter` module has been and features added to `Circulator` itself.
|
data/README.md
CHANGED
@@ -39,7 +39,7 @@ gem install circulator
|
|
39
39
|
|
40
40
|
```ruby
|
41
41
|
class Order
|
42
|
-
extend Circulator
|
42
|
+
extend Circulator
|
43
43
|
|
44
44
|
attr_accessor :status
|
45
45
|
|
@@ -77,7 +77,7 @@ order.status_deliver # => :delivered
|
|
77
77
|
|
78
78
|
```ruby
|
79
79
|
class Document
|
80
|
-
extend Circulator
|
80
|
+
extend Circulator
|
81
81
|
|
82
82
|
attr_accessor :state, :reviewed_by
|
83
83
|
|
@@ -97,7 +97,7 @@ end
|
|
97
97
|
|
98
98
|
```ruby
|
99
99
|
class Task
|
100
|
-
extend Circulator
|
100
|
+
extend Circulator
|
101
101
|
|
102
102
|
attr_accessor :priority, :urgency_level
|
103
103
|
|
@@ -114,7 +114,7 @@ end
|
|
114
114
|
|
115
115
|
```ruby
|
116
116
|
class Server
|
117
|
-
extend Circulator
|
117
|
+
extend Circulator
|
118
118
|
|
119
119
|
attr_accessor :power_state, :network_state
|
120
120
|
|
@@ -147,7 +147,7 @@ end
|
|
147
147
|
|
148
148
|
```ruby
|
149
149
|
class Payment
|
150
|
-
extend Circulator
|
150
|
+
extend Circulator
|
151
151
|
|
152
152
|
attr_accessor :status, :processed_at
|
153
153
|
|
data/lib/circulator/flow.rb
CHANGED
data/lib/circulator/version.rb
CHANGED
data/lib/circulator.rb
CHANGED
@@ -1,3 +1,241 @@
|
|
1
1
|
require "circulator/version"
|
2
|
-
require "circulator/diverter"
|
3
2
|
require "circulator/flow"
|
3
|
+
|
4
|
+
module Circulator
|
5
|
+
# Declare a flow for an attribute.
|
6
|
+
#
|
7
|
+
# Specify the attribute to be used for states and actions.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# flow(:status) do
|
12
|
+
# state :pending do
|
13
|
+
# action :approve, to: :approved
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# The above declares a flow for the `status` attribute. When in the `pending`
|
18
|
+
# state, the `approve` action will transition the `status` to `approved`.
|
19
|
+
#
|
20
|
+
# This creates a `status_approve` method which will change the state in memory.
|
21
|
+
#
|
22
|
+
# You will also have a instance method `flow` which will allow you to specify
|
23
|
+
# the action to take on the attribute.
|
24
|
+
#
|
25
|
+
# Example:
|
26
|
+
#
|
27
|
+
# test_object.status_approve
|
28
|
+
# # OR
|
29
|
+
# test_object.flow(:approve, :status)
|
30
|
+
#
|
31
|
+
# You can also provide a block to receive arguments
|
32
|
+
#
|
33
|
+
# Example:
|
34
|
+
#
|
35
|
+
# flow(:status) do
|
36
|
+
# state :pending do
|
37
|
+
# action :approve, to: :approved do |*args, **kwargs|
|
38
|
+
# @args_received = {args: args, kwargs: kwargs}
|
39
|
+
# end
|
40
|
+
# action_allowed(:approve) { true } # Optional. Check some value on self
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# The block will be evalutaed on the instance of the class declaring the flow.
|
45
|
+
# So `self` inside that action block will be the instance of the class.
|
46
|
+
#
|
47
|
+
# Example:
|
48
|
+
#
|
49
|
+
# test_object.status_approve("arg1", "arg2", key: "value")
|
50
|
+
# # @args_received will be {args: ["arg1", "arg2"], kwargs: {key: "value"}}
|
51
|
+
#
|
52
|
+
# If the action is not allowed, the transition will not be executed.
|
53
|
+
#
|
54
|
+
# Example:
|
55
|
+
#
|
56
|
+
# flow(:status) do
|
57
|
+
# state :pending do
|
58
|
+
# action :approve, to: :approved do
|
59
|
+
# @args_received = {args: args, kwargs: kwargs}
|
60
|
+
# end
|
61
|
+
# action_allowed(:approve) { false }
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# test_object.status_approve
|
66
|
+
# # Will not transition because the action is not allowed
|
67
|
+
#
|
68
|
+
# You may also specify the `allow_if` option to check a condition before
|
69
|
+
# the action is allowed. The callable will be evaluated on the instance of
|
70
|
+
# the class declaring the flow. So `self` inside that block will be the
|
71
|
+
# instance of the class.
|
72
|
+
#
|
73
|
+
# Example:
|
74
|
+
#
|
75
|
+
# flow(:status) do
|
76
|
+
# state :pending do
|
77
|
+
# action :approve, to: :approved, allow_if: -> { true }
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# test_object.status_approve
|
82
|
+
# # Will transition to :approved if the condition is true
|
83
|
+
#
|
84
|
+
# If you declare states separately, for example in an enum, you can use the
|
85
|
+
# `action` method to declare the action on the attribute.
|
86
|
+
#
|
87
|
+
# Example:
|
88
|
+
#
|
89
|
+
# enum :status, {pending: 0, approved: 1, rejected: 2}
|
90
|
+
# flow(:status) do
|
91
|
+
# action :approve, to: :approved, from: :pending
|
92
|
+
# action :reject, to: :rejected, from: :approved do |rejected_at|
|
93
|
+
# self.rejected_at = rejected_at
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# test_object.status_approve
|
98
|
+
# # Will transition to :approved
|
99
|
+
# test_object.status_reject
|
100
|
+
# # Will transition to :rejected and set the rejected_at attribute
|
101
|
+
#
|
102
|
+
# By default, if there is no transition for the current state, the flow will
|
103
|
+
# raise an error. You can specify a no_action block to handle this case.
|
104
|
+
#
|
105
|
+
# Example:
|
106
|
+
#
|
107
|
+
# flow(:status) do
|
108
|
+
# no_action { |attribute_name, action| raise "Nope!" }
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# test_object.status_approve
|
112
|
+
# # Will raise an error
|
113
|
+
#
|
114
|
+
# You can also provide a custom action for other behavior for a set of states and
|
115
|
+
# use the `to` option as a callable to set the attribute.
|
116
|
+
#
|
117
|
+
# Example:
|
118
|
+
#
|
119
|
+
# flow(:status) do
|
120
|
+
# action :unknown, to: -> { status }, from: [:enforcing, :monitoring, :ignoring] do |signal|
|
121
|
+
# raise UnhandledSignalError, signal
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# test_object.flow(:unknown, :status, "signal")
|
126
|
+
# # Will raise an UnhandledSignalError
|
127
|
+
#
|
128
|
+
def flow(attribute_name, model: to_s, &block)
|
129
|
+
@flows ||= {}
|
130
|
+
model_key = Circulator.model_key(model)
|
131
|
+
@flows[model_key] ||= {}
|
132
|
+
@flows[model_key][attribute_name] = Flow.new(model, attribute_name, &block)
|
133
|
+
|
134
|
+
flow_module = ancestors.find { |ancestor|
|
135
|
+
ancestor.name.to_s =~ /FlowMethods/
|
136
|
+
} || Module.new.tap do |mod|
|
137
|
+
include mod
|
138
|
+
|
139
|
+
const_set(:FlowMethods, mod)
|
140
|
+
end
|
141
|
+
|
142
|
+
object = if model == to_s
|
143
|
+
nil
|
144
|
+
else
|
145
|
+
Circulator.methodize_name(model)
|
146
|
+
end
|
147
|
+
|
148
|
+
@flows.dig(model_key, attribute_name).transition_map.each do |action, transitions|
|
149
|
+
define_flow_method(attribute_name:, action:, transitions:, object:, owner: flow_module)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
alias_method :circulator, :flow
|
153
|
+
|
154
|
+
def define_flow_method(attribute_name:, action:, transitions:, object:, owner:)
|
155
|
+
object_attribute_method = [object, attribute_name, action].compact.join("_")
|
156
|
+
raise ArgumentError, "Method already defined: #{object_attribute_method}" if owner.method_defined?(object_attribute_method)
|
157
|
+
|
158
|
+
owner.define_method(object_attribute_method) do |*args, flow_target: self, **kwargs, &block|
|
159
|
+
current_value = flow_target.send(attribute_name)
|
160
|
+
|
161
|
+
transition = if current_value.respond_to?(:to_sym)
|
162
|
+
transitions[current_value.to_sym]
|
163
|
+
else
|
164
|
+
transitions[current_value]
|
165
|
+
end
|
166
|
+
|
167
|
+
unless transition
|
168
|
+
flow_target.instance_exec(attribute_name, action, &flows.dig(Circulator.model_key(flow_target), attribute_name).no_action)
|
169
|
+
return
|
170
|
+
end
|
171
|
+
|
172
|
+
if transition[:allow_if]
|
173
|
+
return unless flow_target.instance_exec(*args, **kwargs, &transition[:allow_if])
|
174
|
+
end
|
175
|
+
|
176
|
+
if transition[:block]
|
177
|
+
flow_target.instance_exec(*args, **kwargs, &transition[:block])
|
178
|
+
end
|
179
|
+
|
180
|
+
if transition[:to].respond_to?(:call)
|
181
|
+
flow_target.send("#{attribute_name}=", flow_target.instance_exec(*args, **kwargs, &transition[:to]))
|
182
|
+
else
|
183
|
+
flow_target.send("#{attribute_name}=", transition[:to])
|
184
|
+
end.tap do
|
185
|
+
if block
|
186
|
+
flow_target.instance_exec(*args, **kwargs, &block)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
module_function def model_key(object)
|
193
|
+
if object.is_a?(String)
|
194
|
+
if object.start_with?("#<Class:")
|
195
|
+
"anonymous_#{object.split("0x")[1]}".sub(">", "")
|
196
|
+
else
|
197
|
+
object
|
198
|
+
end
|
199
|
+
else
|
200
|
+
model_key(object.class.name || object.class.to_s)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
module_function def methodize_name(name)
|
205
|
+
name.split("::").map { |part| part.gsub(/([a-z])([A-Z])/, '\1_\2') }.join("_").downcase
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.extended(base)
|
209
|
+
base.include(InstanceMethods)
|
210
|
+
base.singleton_class.attr_reader :flows
|
211
|
+
end
|
212
|
+
|
213
|
+
module InstanceMethods
|
214
|
+
# Use this method to call an action on the attribute.
|
215
|
+
#
|
216
|
+
# Example:
|
217
|
+
#
|
218
|
+
# test_object.flow(:approve, :status)
|
219
|
+
# test_object.flow(:approve, :status, "arg1", "arg2", key: "value")
|
220
|
+
def flow(action, attribute, *args, flow_target: self, **kwargs, &block)
|
221
|
+
target_name = if flow_target != self
|
222
|
+
Circulator.methodize_name(Circulator.model_key(flow_target))
|
223
|
+
end
|
224
|
+
external_attribute_name = [target_name, attribute].compact.join("_")
|
225
|
+
method_name = "#{external_attribute_name}_#{action}"
|
226
|
+
if respond_to?(method_name)
|
227
|
+
send(method_name, *args, flow_target:, **kwargs, &block)
|
228
|
+
elsif flow_target.respond_to?(method_name)
|
229
|
+
flow_target.send(method_name, *args, **kwargs, &block)
|
230
|
+
else
|
231
|
+
raise "Invalid action for the current state of #{attribute} (#{flow_target.send(attribute).inspect}): #{action}"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
def flows
|
238
|
+
self.class.flows
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: circulator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jim Gay
|
@@ -20,7 +20,6 @@ files:
|
|
20
20
|
- README.md
|
21
21
|
- Rakefile
|
22
22
|
- lib/circulator.rb
|
23
|
-
- lib/circulator/diverter.rb
|
24
23
|
- lib/circulator/flow.rb
|
25
24
|
- lib/circulator/version.rb
|
26
25
|
homepage: https://github.com/SOFware/circulator
|
data/lib/circulator/diverter.rb
DELETED
@@ -1,242 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Circulator
|
4
|
-
module Diverter
|
5
|
-
# Declare a flow for an attribute.
|
6
|
-
#
|
7
|
-
# Specify the attribute to be used for states and actions.
|
8
|
-
#
|
9
|
-
# Example:
|
10
|
-
#
|
11
|
-
# flow(:status) do
|
12
|
-
# state :pending do
|
13
|
-
# action :approve, to: :approved
|
14
|
-
# end
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# The above declares a flow for the `status` attribute. When in the `pending`
|
18
|
-
# state, the `approve` action will transition the `status` to `approved`.
|
19
|
-
#
|
20
|
-
# This creates a `status_approve` method which will change the state in memory.
|
21
|
-
#
|
22
|
-
# You will also have a instance method `flow` which will allow you to specify
|
23
|
-
# the action to take on the attribute.
|
24
|
-
#
|
25
|
-
# Example:
|
26
|
-
#
|
27
|
-
# test_object.status_approve
|
28
|
-
# # OR
|
29
|
-
# test_object.flow(:approve, :status)
|
30
|
-
#
|
31
|
-
# You can also provide a block to receive arguments
|
32
|
-
#
|
33
|
-
# Example:
|
34
|
-
#
|
35
|
-
# flow(:status) do
|
36
|
-
# state :pending do
|
37
|
-
# action :approve, to: :approved do |*args, **kwargs|
|
38
|
-
# @args_received = {args: args, kwargs: kwargs}
|
39
|
-
# end
|
40
|
-
# action_allowed(:approve) { true } # Optional. Check some value on self
|
41
|
-
# end
|
42
|
-
# end
|
43
|
-
#
|
44
|
-
# The block will be evalutaed on the instance of the class declaring the flow.
|
45
|
-
# So `self` inside that action block will be the instance of the class.
|
46
|
-
#
|
47
|
-
# Example:
|
48
|
-
#
|
49
|
-
# test_object.status_approve("arg1", "arg2", key: "value")
|
50
|
-
# # @args_received will be {args: ["arg1", "arg2"], kwargs: {key: "value"}}
|
51
|
-
#
|
52
|
-
# If the action is not allowed, the transition will not be executed.
|
53
|
-
#
|
54
|
-
# Example:
|
55
|
-
#
|
56
|
-
# flow(:status) do
|
57
|
-
# state :pending do
|
58
|
-
# action :approve, to: :approved do
|
59
|
-
# @args_received = {args: args, kwargs: kwargs}
|
60
|
-
# end
|
61
|
-
# action_allowed(:approve) { false }
|
62
|
-
# end
|
63
|
-
# end
|
64
|
-
#
|
65
|
-
# test_object.status_approve
|
66
|
-
# # Will not transition because the action is not allowed
|
67
|
-
#
|
68
|
-
# You may also specify the `allow_if` option to check a condition before
|
69
|
-
# the action is allowed. The callable will be evaluated on the instance of
|
70
|
-
# the class declaring the flow. So `self` inside that block will be the
|
71
|
-
# instance of the class.
|
72
|
-
#
|
73
|
-
# Example:
|
74
|
-
#
|
75
|
-
# flow(:status) do
|
76
|
-
# state :pending do
|
77
|
-
# action :approve, to: :approved, allow_if: -> { true }
|
78
|
-
# end
|
79
|
-
# end
|
80
|
-
#
|
81
|
-
# test_object.status_approve
|
82
|
-
# # Will transition to :approved if the condition is true
|
83
|
-
#
|
84
|
-
# If you declare states separately, for example in an enum, you can use the
|
85
|
-
# `action` method to declare the action on the attribute.
|
86
|
-
#
|
87
|
-
# Example:
|
88
|
-
#
|
89
|
-
# enum :status, {pending: 0, approved: 1, rejected: 2}
|
90
|
-
# flow(:status) do
|
91
|
-
# action :approve, to: :approved, from: :pending
|
92
|
-
# action :reject, to: :rejected, from: :approved do |rejected_at|
|
93
|
-
# self.rejected_at = rejected_at
|
94
|
-
# end
|
95
|
-
# end
|
96
|
-
#
|
97
|
-
# test_object.status_approve
|
98
|
-
# # Will transition to :approved
|
99
|
-
# test_object.status_reject
|
100
|
-
# # Will transition to :rejected and set the rejected_at attribute
|
101
|
-
#
|
102
|
-
# By default, if there is no transition for the current state, the flow will
|
103
|
-
# raise an error. You can specify a no_action block to handle this case.
|
104
|
-
#
|
105
|
-
# Example:
|
106
|
-
#
|
107
|
-
# flow(:status) do
|
108
|
-
# no_action { |attribute_name, action| raise "Nope!" }
|
109
|
-
# end
|
110
|
-
#
|
111
|
-
# test_object.status_approve
|
112
|
-
# # Will raise an error
|
113
|
-
#
|
114
|
-
# You can also provide a custom action for other behavior for a set of states and
|
115
|
-
# use the `to` option as a callable to set the attribute.
|
116
|
-
#
|
117
|
-
# Example:
|
118
|
-
#
|
119
|
-
# flow(:status) do
|
120
|
-
# action :unknown, to: -> { status }, from: [:enforcing, :monitoring, :ignoring] do |signal|
|
121
|
-
# raise UnhandledSignalError, signal
|
122
|
-
# end
|
123
|
-
# end
|
124
|
-
#
|
125
|
-
# test_object.flow(:unknown, :status, "signal")
|
126
|
-
# # Will raise an UnhandledSignalError
|
127
|
-
#
|
128
|
-
def flow(attribute_name, model: to_s, &block)
|
129
|
-
@flows ||= {}
|
130
|
-
model_key = Circulator::Diverter.model_key(model)
|
131
|
-
@flows[model_key] ||= {}
|
132
|
-
@flows[model_key][attribute_name] = Circulator::Flow.new(model, attribute_name, &block)
|
133
|
-
|
134
|
-
flow_module = ancestors.find { |ancestor|
|
135
|
-
ancestor.name.to_s =~ /FlowMethods/
|
136
|
-
} || Module.new.tap do |mod|
|
137
|
-
include mod
|
138
|
-
|
139
|
-
const_set(:FlowMethods, mod)
|
140
|
-
end
|
141
|
-
|
142
|
-
object = if model == to_s
|
143
|
-
nil
|
144
|
-
else
|
145
|
-
Circulator::Diverter.methodize_name(model)
|
146
|
-
end
|
147
|
-
|
148
|
-
@flows.dig(model_key, attribute_name).transition_map.each do |action, transitions|
|
149
|
-
define_flow_method(attribute_name:, action:, transitions:, object:, owner: flow_module)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
alias_method :circulator, :flow
|
153
|
-
|
154
|
-
def define_flow_method(attribute_name:, action:, transitions:, object:, owner:)
|
155
|
-
object_attribute_method = [object, attribute_name, action].compact.join("_")
|
156
|
-
raise ArgumentError, "Method already defined: #{object_attribute_method}" if owner.method_defined?(object_attribute_method)
|
157
|
-
|
158
|
-
owner.define_method(object_attribute_method) do |*args, flow_target: self, **kwargs, &block|
|
159
|
-
current_value = flow_target.send(attribute_name)
|
160
|
-
|
161
|
-
transition = if current_value.respond_to?(:to_sym)
|
162
|
-
transitions[current_value.to_sym]
|
163
|
-
else
|
164
|
-
transitions[current_value]
|
165
|
-
end
|
166
|
-
|
167
|
-
unless transition
|
168
|
-
flow_target.instance_exec(attribute_name, action, &flows.dig(Circulator::Diverter.model_key(flow_target), attribute_name).no_action)
|
169
|
-
return
|
170
|
-
end
|
171
|
-
|
172
|
-
if transition[:allow_if]
|
173
|
-
return unless flow_target.instance_exec(*args, **kwargs, &transition[:allow_if])
|
174
|
-
end
|
175
|
-
|
176
|
-
if transition[:block]
|
177
|
-
flow_target.instance_exec(*args, **kwargs, &transition[:block])
|
178
|
-
end
|
179
|
-
|
180
|
-
if transition[:to].respond_to?(:call)
|
181
|
-
flow_target.send("#{attribute_name}=", flow_target.instance_exec(*args, **kwargs, &transition[:to]))
|
182
|
-
else
|
183
|
-
flow_target.send("#{attribute_name}=", transition[:to])
|
184
|
-
end.tap do
|
185
|
-
if block
|
186
|
-
flow_target.instance_exec(*args, **kwargs, &block)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
module_function def model_key(object)
|
193
|
-
if object.is_a?(String)
|
194
|
-
if object.start_with?("#<Class:")
|
195
|
-
"anonymous_#{object.split("0x")[1]}".sub(">", "")
|
196
|
-
else
|
197
|
-
object
|
198
|
-
end
|
199
|
-
else
|
200
|
-
model_key(object.class.name || object.class.to_s)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
module_function def methodize_name(name)
|
205
|
-
name.split("::").map { |part| part.gsub(/([a-z])([A-Z])/, '\1_\2') }.join("_").downcase
|
206
|
-
end
|
207
|
-
|
208
|
-
def self.extended(base)
|
209
|
-
base.include(InstanceMethods)
|
210
|
-
base.singleton_class.attr_reader :flows
|
211
|
-
end
|
212
|
-
|
213
|
-
module InstanceMethods
|
214
|
-
# Use this method to call an action on the attribute.
|
215
|
-
#
|
216
|
-
# Example:
|
217
|
-
#
|
218
|
-
# test_object.flow(:approve, :status)
|
219
|
-
# test_object.flow(:approve, :status, "arg1", "arg2", key: "value")
|
220
|
-
def flow(action, attribute, *args, flow_target: self, **kwargs, &block)
|
221
|
-
target_name = if flow_target != self
|
222
|
-
Circulator::Diverter.methodize_name(Circulator::Diverter.model_key(flow_target))
|
223
|
-
end
|
224
|
-
external_attribute_name = [target_name, attribute].compact.join("_")
|
225
|
-
method_name = "#{external_attribute_name}_#{action}"
|
226
|
-
if respond_to?(method_name)
|
227
|
-
send(method_name, *args, flow_target:, **kwargs, &block)
|
228
|
-
elsif flow_target.respond_to?(method_name)
|
229
|
-
flow_target.send(method_name, *args, **kwargs, &block)
|
230
|
-
else
|
231
|
-
raise "Invalid action for the current state of #{attribute} (#{flow_target.send(attribute).inspect}): #{action}"
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
private
|
236
|
-
|
237
|
-
def flows
|
238
|
-
self.class.flows
|
239
|
-
end
|
240
|
-
end
|
241
|
-
end
|
242
|
-
end
|