circulator 2.1.4 → 2.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9126192a7ce18956bc8c27842e52765dac25e9436e322ad259100e5138a5b97f
4
- data.tar.gz: f1e15524d5806a3f0b0455bd006e8ba5e3914b13e046ab0d2acd1dbcb897ea27
3
+ metadata.gz: 4c511772e424334259794bfff3d3507e5740fc98f0a130c66bf1d77a41862f6a
4
+ data.tar.gz: 7ee239ac23cbd6998a6d1689f8d6729983b42a6d6081e092465483ee19013107
5
5
  SHA512:
6
- metadata.gz: 03ad68c5d43c45fddf1b7d5c3f84dd845363b2bf8cc1a881463efbd1b1e43d233e196e965dae157726b2ac0b90066d9ec05087f12339667ffd1e9effe0213e54
7
- data.tar.gz: 7590c58b27a608f4d49f6041c9fed8a4fd5a05526cd5c36eba40a12346ce5a63cd6f9f0a6253c48ec6aa7405e9e4b5ed8121fae276404a1a32f4587118453c2f
6
+ metadata.gz: 63e6165e93a276d89ac6c469c3e8b5db41e6b184cde3290a9ff1332992e7f27a06cf1cd8f8b93c32100986c27be3761d0cfbc9ef7c69414b0cd379f32813c018
7
+ data.tar.gz: 586cdd39db8b98b10465e84c02de948a44f39c556ae270ea6a68ea8f90ca8c0b61af37917419dfe599e39e882584101169f932671996edbeeb97d6e9debe806d
data/CHANGELOG.md CHANGED
@@ -5,24 +5,10 @@ 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.4] - 2025-11-03
8
+ ## [2.1.5] - 2025-11-15
9
9
 
10
10
  ### Added
11
11
 
12
- - Symbol-based allow_if support (a96b794)
13
- - Documentation for symbol-based allow_if (d3befc1)
14
- - available_flows method with guard and argument support (122e2ab)
15
- - available_flow? predicate method (122e2ab)
16
- - Documentation for query methods (a75507e)
17
-
18
- ### Changed
19
-
20
- - Validate symbol allow_if at definition time (392eac5)
21
-
22
- ### Removed
23
-
24
- - Redundant respond_to? check (40ff669)
25
-
26
- ### Fixed
27
-
28
- - DOT syntax error for states ending with ? (65b503e)
12
+ - Circulator.extension to define changes to existing state machines. (0f0a50f)
13
+ - Circulator.default_flow_proc to allow for custom storage objects. (7ddc442)
14
+ - Test support for custom flows storage with libraries like Contours::BlendedHash. (c7f1f26)
@@ -2,13 +2,19 @@
2
2
 
3
3
  module Circulator
4
4
  class Flow
5
- def initialize(klass, attribute_name, states = Set.new, &block)
5
+ def initialize(klass, attribute_name, states = Set.new, extension: false, flows_proc: Circulator.default_flow_proc, &block)
6
6
  @klass = klass
7
7
  @attribute_name = attribute_name
8
8
  @states = states
9
9
  @no_action = ->(attribute_name, action) { raise "No action found for the current state of #{attribute_name} (#{send(attribute_name)}): #{action}" }
10
- @transition_map = {}
11
- instance_eval(&block)
10
+ @flows_proc = flows_proc
11
+ @transition_map = flows_proc.call
12
+
13
+ # Execute the main flow block
14
+ instance_eval(&block) if block
15
+
16
+ # Apply any registered extensions (unless explicitly disabled)
17
+ apply_extensions unless extension
12
18
  end
13
19
  attr_reader :transition_map
14
20
 
@@ -28,7 +34,7 @@ module Circulator
28
34
  validate_allow_if(allow_if)
29
35
  end
30
36
 
31
- @transition_map[name] ||= {}
37
+ @transition_map[name] ||= @flows_proc.call
32
38
  selected_state = (from == :__not_specified__) ? @current_state : from
33
39
 
34
40
  # Handle nil case specially - convert to [nil] instead of []
@@ -48,8 +54,10 @@ module Circulator
48
54
  @states.add(to_state)
49
55
  end
50
56
 
51
- @transition_map[name][from_state] = {to:, block:}
52
- @transition_map[name][from_state][:allow_if] = allow_if if allow_if
57
+ # Build transition data hash with all keys at once
58
+ transition_data = {to:, block:}
59
+ transition_data[:allow_if] = allow_if if allow_if
60
+ @transition_map[name][from_state] = transition_data
53
61
  end
54
62
  end
55
63
 
@@ -132,5 +140,33 @@ module Circulator
132
140
  raise ArgumentError, "allow_if references invalid states #{invalid_states.inspect} for :#{attribute_name}. Valid states: #{referenced_states.to_a.inspect}"
133
141
  end
134
142
  end
143
+
144
+ def apply_extensions
145
+ # Look up extensions for this class and attribute
146
+ class_name = if @klass.is_a?(Class)
147
+ @klass.name || @klass.to_s
148
+ else
149
+ Circulator.model_key(@klass)
150
+ end
151
+ key = "#{class_name}:#{@attribute_name}"
152
+ extensions = Circulator.extensions[key]
153
+
154
+ # Apply each extension by creating a new Flow and merging its transition_map
155
+ extensions.each do |extension_block|
156
+ extension_flow = Flow.new(@klass, @attribute_name, @states, extension: true, flows_proc: @flows_proc, &extension_block)
157
+ extension_flow.transition_map.each do |action, transitions|
158
+ @transition_map[action] = if @transition_map[action]
159
+ @transition_map[action].merge(transitions)
160
+ else
161
+ transitions
162
+ end
163
+ end
164
+
165
+ # Merge states from extension
166
+ extension_flow.instance_variable_get(:@states).each do |state|
167
+ @states.add(state)
168
+ end
169
+ end
170
+ end
135
171
  end
136
172
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Circulator
4
- VERSION = "2.1.4"
4
+ VERSION = "2.1.5"
5
5
  end
data/lib/circulator.rb CHANGED
@@ -2,6 +2,32 @@ require "circulator/version"
2
2
  require "circulator/flow"
3
3
 
4
4
  module Circulator
5
+ # Global registry for extensions
6
+ @extensions = Hash.new { |h, k| h[k] = [] }
7
+
8
+ @default_flow_proc = ::Hash.method(:new)
9
+ class << self
10
+ attr_reader :extensions
11
+ attr_reader :default_flow_proc
12
+
13
+ # Register an extension for a specific class and attribute
14
+ #
15
+ # Example:
16
+ #
17
+ # Circulator.extension(:Document, :status) do
18
+ # state :pending do
19
+ # action :send_to_legal, to: :legal_review
20
+ # end
21
+ # end
22
+ #
23
+ # Extensions are automatically applied when the class defines its flow
24
+ def extension(class_name, attribute_name, &block)
25
+ raise ArgumentError, "Block required for extension" unless block_given?
26
+
27
+ key = "#{class_name}:#{attribute_name}"
28
+ @extensions[key] << block
29
+ end
30
+ end
5
31
  # Declare a flow for an attribute.
6
32
  #
7
33
  # Specify the attribute to be used for states and actions.
@@ -125,11 +151,12 @@ module Circulator
125
151
  # test_object.flow(:unknown, :status, "signal")
126
152
  # # Will raise an UnhandledSignalError
127
153
  #
128
- def flow(attribute_name, model: to_s, &block)
129
- @flows ||= {}
154
+ def flow(attribute_name, model: to_s, flows_proc: Circulator.default_flow_proc, &block)
155
+ @flows ||= flows_proc.call
130
156
  model_key = Circulator.model_key(model)
131
- @flows[model_key] ||= {}
132
- @flows[model_key][attribute_name] = Flow.new(self, attribute_name, &block)
157
+ @flows[model_key] ||= flows_proc.call
158
+ # Pass the flows_proc to Flow so it can create transition_maps of the same type
159
+ @flows[model_key][attribute_name] = Flow.new(self, attribute_name, flows_proc:, &block)
133
160
 
134
161
  flow_module = ancestors.find { |ancestor|
135
162
  ancestor.name.to_s =~ /FlowMethods/
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
4
+ version: 2.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay