circulator 2.1.10 → 2.1.11
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 +11 -2
- data/README.md +33 -0
- data/lib/circulator/flow.rb +99 -4
- data/lib/circulator/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9232620710fbfe075e26c0ae3bcf1dea0794cdca188b0a9a0ff8529362573ede
|
|
4
|
+
data.tar.gz: c45d7ac1bdf89fe21107905811d36d0ef4dca37370ea89686d5433cef1aeaaf5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bfbc391566c29b7dd18005c56c37c21c9d976eeebc2705f92ea726074b93807daf47528f34e07bb449be3a38bca797067742abc64e1d862162cc18924049b47d
|
|
7
|
+
data.tar.gz: 9edd59a4985b266ad4c590d3e4d05bbe41f381e85a149b84de08adeabf9e1ff141d0a2b5d5dee759e1e1ba8d4dd740c8e33036331694614951994fddab6d4448
|
data/CHANGELOG.md
CHANGED
|
@@ -5,8 +5,17 @@ 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
|
-
## [2.1.
|
|
8
|
+
## [2.1.11] - 2026-02-09
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- action_missing alias for no_action to clarify intent (c576a7c)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Improved inline documentation for no_action and around methods (c576a7c)
|
|
17
|
+
- Added method documentation for Flow DSL methods (2546989)
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
- @no_action internal instance_variable and switched to @action_missing Version: minor (c576a7c)
|
data/README.md
CHANGED
|
@@ -294,6 +294,39 @@ class Payment
|
|
|
294
294
|
end
|
|
295
295
|
```
|
|
296
296
|
|
|
297
|
+
#### Handling Missing Transitions with `action_missing`
|
|
298
|
+
|
|
299
|
+
By default, if you call an action on an object whose current state doesn't define a transition for that action, Circulator raises an error. Use `action_missing` (aliased as `no_action`) to customize this behavior.
|
|
300
|
+
|
|
301
|
+
This is triggered when, for example, you call `order.status_ship` but the object is in `:pending` and `:ship` only has a transition defined from `:processing`.
|
|
302
|
+
|
|
303
|
+
```ruby
|
|
304
|
+
class Order
|
|
305
|
+
extend Circulator
|
|
306
|
+
|
|
307
|
+
attr_accessor :status
|
|
308
|
+
|
|
309
|
+
flow :status do
|
|
310
|
+
action_missing do |attribute_name, action|
|
|
311
|
+
Rails.logger.warn "No #{action} transition from #{send(attribute_name)} for #{attribute_name}"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
state :pending do
|
|
315
|
+
action :process, to: :processing
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
state :processing do
|
|
319
|
+
action :ship, to: :shipped
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
order = Order.new
|
|
325
|
+
order.status = :pending
|
|
326
|
+
|
|
327
|
+
order.status_ship # => logs warning instead of raising
|
|
328
|
+
```
|
|
329
|
+
|
|
297
330
|
#### Wrapping Transitions with `around`
|
|
298
331
|
|
|
299
332
|
Use the `around` block to wrap all transitions in a flow with shared logic. The block receives a `transition` proc that you must call for the transition to execute:
|
data/lib/circulator/flow.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Circulator
|
|
|
6
6
|
@klass = klass
|
|
7
7
|
@attribute_name = attribute_name
|
|
8
8
|
@states = states
|
|
9
|
-
@
|
|
9
|
+
@action_missing = ->(attribute_name, action) { raise "No action found for the current state of #{attribute_name} (#{send(attribute_name)}): #{action}" }
|
|
10
10
|
@flows_proc = flows_proc
|
|
11
11
|
@transition_map = flows_proc.call
|
|
12
12
|
|
|
@@ -18,6 +18,18 @@ module Circulator
|
|
|
18
18
|
end
|
|
19
19
|
attr_reader :transition_map
|
|
20
20
|
|
|
21
|
+
# Declares a state in the flow. The block is evaluated in the context
|
|
22
|
+
# of the flow, allowing you to define actions that transition from
|
|
23
|
+
# this state. A state with no block acts as a terminal state.
|
|
24
|
+
#
|
|
25
|
+
# flow(:status) do
|
|
26
|
+
# state :pending do
|
|
27
|
+
# action :approve, to: :approved
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# state :approved # terminal state
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
21
33
|
def state(name, &block)
|
|
22
34
|
name = name.to_sym if name.respond_to?(:to_sym)
|
|
23
35
|
@states.add(name)
|
|
@@ -26,6 +38,34 @@ module Circulator
|
|
|
26
38
|
remove_instance_variable(:@current_state)
|
|
27
39
|
end
|
|
28
40
|
|
|
41
|
+
# Declares a transition from one or more states to a destination state.
|
|
42
|
+
# Must be called inside a +state+ block, or with an explicit +from:+ option.
|
|
43
|
+
#
|
|
44
|
+
# Generates a method named +<attribute>_<action>+ on the class that
|
|
45
|
+
# performs the transition when called.
|
|
46
|
+
#
|
|
47
|
+
# ==== Options
|
|
48
|
+
#
|
|
49
|
+
# +to+:: The destination state (Symbol) or a callable that returns
|
|
50
|
+
# the destination state at runtime.
|
|
51
|
+
# +from+:: One or more source states. Defaults to the enclosing
|
|
52
|
+
# +state+ block. Pass an Array to define the same action
|
|
53
|
+
# from multiple states.
|
|
54
|
+
# +allow_if+:: A guard condition that must be truthy for the transition
|
|
55
|
+
# to proceed. Accepts a Proc, Symbol (method name), Hash
|
|
56
|
+
# (checks another flow's state), or Array of those.
|
|
57
|
+
# +&block+:: A block executed on the instance during the transition,
|
|
58
|
+
# before the state is changed.
|
|
59
|
+
#
|
|
60
|
+
# state :pending do
|
|
61
|
+
# action :approve, to: :approved, allow_if: :reviewer? do
|
|
62
|
+
# self.approved_at = Time.now
|
|
63
|
+
# end
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
# # Or with an explicit from:
|
|
67
|
+
# action :cancel, from: [:pending, :processing], to: :cancelled
|
|
68
|
+
#
|
|
29
69
|
def action(name, to:, from: :__not_specified__, allow_if: nil, &block)
|
|
30
70
|
raise "You must be in a state block or have a `from` option to declare an action" unless defined?(@current_state) || from != :__not_specified__
|
|
31
71
|
|
|
@@ -61,6 +101,22 @@ module Circulator
|
|
|
61
101
|
end
|
|
62
102
|
end
|
|
63
103
|
|
|
104
|
+
# Attaches an +allow_if+ guard to an existing action after it has been
|
|
105
|
+
# defined. This is useful when the guard logic needs to be defined
|
|
106
|
+
# separately from the action itself, such as in an extension.
|
|
107
|
+
#
|
|
108
|
+
# Must be called inside a +state+ block, or with an explicit +from:+ option.
|
|
109
|
+
# The action must already exist in the transition map.
|
|
110
|
+
#
|
|
111
|
+
# Example:
|
|
112
|
+
#
|
|
113
|
+
# flow(:status) do
|
|
114
|
+
# state :pending do
|
|
115
|
+
# action :approve, to: :approved
|
|
116
|
+
# action_allowed(:approve) { current_user.admin? }
|
|
117
|
+
# end
|
|
118
|
+
# end
|
|
119
|
+
#
|
|
64
120
|
def action_allowed(name, from: :__not_specified__, &block)
|
|
65
121
|
raise "You must be in a state block or have a `from` option to declare an action" unless defined?(@current_state) || from != :__not_specified__
|
|
66
122
|
|
|
@@ -80,14 +136,53 @@ module Circulator
|
|
|
80
136
|
end
|
|
81
137
|
end
|
|
82
138
|
|
|
83
|
-
|
|
139
|
+
# Called when an action is invoked but no transition is defined for the
|
|
140
|
+
# current state. For example, if an object is in :approved and you call
|
|
141
|
+
# an action that only has a transition from :pending, this block runs.
|
|
142
|
+
#
|
|
143
|
+
# By default, raises an error. Override to silently ignore, log, or
|
|
144
|
+
# handle the missing transition however you like.
|
|
145
|
+
#
|
|
146
|
+
# The block receives two arguments: the attribute name and the action name.
|
|
147
|
+
#
|
|
148
|
+
# Example:
|
|
149
|
+
#
|
|
150
|
+
# flow(:status) do
|
|
151
|
+
# action_missing do |attribute_name, action|
|
|
152
|
+
# Rails.logger.warn("No transition for #{attribute_name} in #{action}")
|
|
153
|
+
# end
|
|
154
|
+
# end
|
|
155
|
+
def action_missing(&block)
|
|
84
156
|
if block_given?
|
|
85
|
-
@
|
|
157
|
+
@action_missing = block
|
|
86
158
|
else
|
|
87
|
-
@
|
|
159
|
+
@action_missing
|
|
88
160
|
end
|
|
89
161
|
end
|
|
162
|
+
alias_method :no_action, :action_missing
|
|
90
163
|
|
|
164
|
+
# Wraps every transition in this flow. The block receives a lambda that
|
|
165
|
+
# executes the transition logic (guard check, state change, and any
|
|
166
|
+
# transition block). You must call +transition.call+ for the transition
|
|
167
|
+
# to actually happen — omitting it prevents the state change entirely.
|
|
168
|
+
#
|
|
169
|
+
# Useful for wrapping transitions in database transactions, logging,
|
|
170
|
+
# instrumentation, or any before/after behavior.
|
|
171
|
+
#
|
|
172
|
+
# Example:
|
|
173
|
+
#
|
|
174
|
+
# flow(:status) do
|
|
175
|
+
# around do |transition|
|
|
176
|
+
# ActiveRecord::Base.transaction do
|
|
177
|
+
# transition.call
|
|
178
|
+
# end
|
|
179
|
+
# end
|
|
180
|
+
#
|
|
181
|
+
# state :pending do
|
|
182
|
+
# action :approve, to: :approved
|
|
183
|
+
# end
|
|
184
|
+
# end
|
|
185
|
+
#
|
|
91
186
|
def around(&block)
|
|
92
187
|
if block_given?
|
|
93
188
|
@around = block
|
data/lib/circulator/version.rb
CHANGED
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: 2.1.
|
|
4
|
+
version: 2.1.11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jim Gay
|
|
@@ -46,7 +46,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
47
|
version: '0'
|
|
48
48
|
requirements: []
|
|
49
|
-
rubygems_version:
|
|
49
|
+
rubygems_version: 3.6.9
|
|
50
50
|
specification_version: 4
|
|
51
51
|
summary: Simple state machine
|
|
52
52
|
test_files: []
|