circulator 2.1.5 → 2.1.6

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: 4c511772e424334259794bfff3d3507e5740fc98f0a130c66bf1d77a41862f6a
4
- data.tar.gz: 7ee239ac23cbd6998a6d1689f8d6729983b42a6d6081e092465483ee19013107
3
+ metadata.gz: 128d49edd784a3a0cc8ebc72ee4ca9be581bbea4ab57ff795a7c7435a52e3ddb
4
+ data.tar.gz: ae4a7ca3dd48197e944c95516f862e9f08cbc082ad098d0ef3d589fe351d59c7
5
5
  SHA512:
6
- metadata.gz: 63e6165e93a276d89ac6c469c3e8b5db41e6b184cde3290a9ff1332992e7f27a06cf1cd8f8b93c32100986c27be3761d0cfbc9ef7c69414b0cd379f32813c018
7
- data.tar.gz: 586cdd39db8b98b10465e84c02de948a44f39c556ae270ea6a68ea8f90ca8c0b61af37917419dfe599e39e882584101169f932671996edbeeb97d6e9debe806d
6
+ metadata.gz: 5792c5c3fc86dc3a7d75fd619229426a3a8ae8198bee46fe28d7ec54f3d78ca374732c71281177fb53a8e54d5f1ed256af8163cd70420739b4450fb0f5b0589d
7
+ data.tar.gz: f7fc9e7bf3e10331306a3161c7feff64e09b0dd1933f52f054f98076256c2287e40c6ddd7ac7b6c7eee39bb55442ca3f3045179cc889378d87b9c59eb74dc971
data/CHANGELOG.md CHANGED
@@ -5,10 +5,14 @@ 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.5] - 2025-11-15
8
+ ## [2.1.6] - 2025-12-08
9
+
10
+ ### Changed
11
+
12
+ - Dependabot config to follow a 14 day cooldown. (6aabc0a)
9
13
 
10
14
  ### Added
11
15
 
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)
16
+ - Documentation for extension system and Circulator.extension API (be7c6fc)
17
+ - --all flag to automatically discover and generate diagrams for all classes with Circulator flows (f4dbbfc)
18
+ - Automatic eager loading of Rails application classes when using --all (f4dbbfc)
data/README.md CHANGED
@@ -285,10 +285,52 @@ class Payment
285
285
  end
286
286
  ```
287
287
 
288
+ #### Extending Flows
289
+
290
+ You can extend existing flows using `Circulator.extension`:
291
+
292
+ ```ruby
293
+ class Document
294
+ extend Circulator
295
+
296
+ attr_accessor :status
297
+
298
+ flow :status do
299
+ state :draft do
300
+ action :submit, to: :review
301
+ end
302
+
303
+ state :review do
304
+ action :approve, to: :approved
305
+ end
306
+
307
+ state :approved
308
+ end
309
+ end
310
+
311
+ # Add additional states and transitions
312
+ Circulator.extension(:Document, :status) do
313
+ state :review do
314
+ action :reject, to: :rejected
315
+ end
316
+
317
+ state :rejected do
318
+ action :revise, to: :draft
319
+ end
320
+ end
321
+
322
+ doc = Document.new
323
+ doc.status = :review
324
+ doc.status_reject # => :rejected (from extension)
325
+ doc.status_revise # => :draft (from extension)
326
+ ```
327
+
288
328
  ### Generating Diagrams
289
329
 
290
330
  You can generate diagrams for your Circulator models using the `circulator-diagram` executable. By default, it will generate a DOT file. You can also generate a PlantUML file by passing the `-f plantuml` option.
291
331
 
332
+ #### Generate a diagram for a specific model:
333
+
292
334
  ```bash
293
335
  bundle exec circulator-diagram MODEL_NAME
294
336
  ```
@@ -297,6 +339,30 @@ bundle exec circulator-diagram MODEL_NAME
297
339
  bundle exec circulator-diagram MODEL_NAME -f plantuml
298
340
  ```
299
341
 
342
+ #### Generate diagrams for all models with Circulator flows:
343
+
344
+ Use the `--all` option to automatically find and generate diagrams for all classes that have Circulator flows defined:
345
+
346
+ ```bash
347
+ bundle exec circulator-diagram --all
348
+ ```
349
+
350
+ ```bash
351
+ bundle exec circulator-diagram --all -f plantuml
352
+ ```
353
+
354
+ The `--all` option will:
355
+ - Automatically discover all classes with Circulator flows (including classes that inherit from a parent that extends Circulator)
356
+ - Eager load Rails application classes if running in a Rails environment
357
+ - Generate diagrams for each class found
358
+ - Use the same output directory and format options as single-model generation
359
+
360
+ #### Other options:
361
+
362
+ - `-d, --directory DIRECTORY` - Specify output directory (default: `docs`)
363
+ - `-s, --separate` - Generate separate diagram files for each flow attribute
364
+ - `-r, --require FILE` - Require a file before loading models (e.g., `config/environment`)
365
+
300
366
  ## Why Circulator?
301
367
 
302
368
  Circulator distinguishes itself from other Ruby state machine libraries through its simplicity and flexibility:
@@ -8,17 +8,21 @@ require_relative "../lib/circulator/dot"
8
8
  require_relative "../lib/circulator/plantuml"
9
9
 
10
10
  # Parse command-line options
11
- options = {format: "dot", require: nil, directory: "docs", separate: false}
11
+ options = {format: "dot", require: nil, directory: "docs", separate: false, all: false}
12
12
  parser = OptionParser.new do |opts|
13
- opts.banner = "Usage: circulator-diagram MODEL_NAME [options]"
13
+ opts.banner = "Usage: circulator-diagram [MODEL_NAME | --all] [options]"
14
14
  opts.separator ""
15
15
  opts.separator "Generate diagram files for Circulator state machine flows"
16
16
  opts.separator ""
17
17
  opts.separator "Arguments:"
18
- opts.separator " MODEL_NAME Name of the model class with Circulator flows"
18
+ opts.separator " MODEL_NAME Name of the model class with Circulator flows (required unless --all is used)"
19
19
  opts.separator ""
20
20
  opts.separator "Options:"
21
21
 
22
+ opts.on("-a", "--all", "Generate diagrams for all classes with Circulator in their ancestry") do
23
+ options[:all] = true
24
+ end
25
+
22
26
  opts.on("-f", "--format FORMAT", ["dot", "plantuml"], "Output format (dot, plantuml). Default: dot") do |format|
23
27
  options[:format] = format
24
28
  end
@@ -54,15 +58,15 @@ rescue OptionParser::InvalidOption => e
54
58
  exit 1
55
59
  end
56
60
 
57
- # Check for required model name argument
58
- if ARGV.empty?
59
- warn "Error: MODEL_NAME is required"
61
+ # Check for required model name argument or --all flag
62
+ if ARGV.empty? && !options[:all]
63
+ warn "Error: Either MODEL_NAME or --all is required"
60
64
  warn ""
61
65
  warn parser
62
66
  exit 1
63
67
  end
64
68
 
65
- model_name = ARGV[0]
69
+ model_name = ARGV[0] unless options[:all]
66
70
 
67
71
  # Load the application environment
68
72
  # Priority: -r option > config/environment.rb > nothing
@@ -79,22 +83,63 @@ elsif File.exist?("config/environment.rb")
79
83
  require File.expand_path("config/environment.rb")
80
84
  end
81
85
 
82
- # Try to constantize the model name
83
- begin
84
- model_class = Object.const_get(model_name)
85
- rescue NameError
86
- warn "Error: Model '#{model_name}' not found"
87
- warn "Make sure the model is loaded in your environment"
88
- exit 1
86
+ # Find all classes with Circulator in their ancestry
87
+ def find_circulator_classes
88
+ classes = []
89
+
90
+ # Try to force-load classes that might not be loaded yet
91
+ # This is especially important for Rails apps with lazy loading
92
+ if defined?(Rails) && Rails.application
93
+ begin
94
+ Rails.application.eager_load!
95
+ rescue => e
96
+ warn "Warning: Could not eager load Rails application: #{e.message}"
97
+ end
98
+ end
99
+
100
+ ObjectSpace.each_object(Class) do |klass|
101
+ # Skip anonymous classes and classes without names
102
+ next if klass.name.nil? || klass.name.empty?
103
+
104
+ # Check if the class responds to :flows (set up when Circulator is extended)
105
+ next unless klass.respond_to?(:flows)
106
+
107
+ begin
108
+ flows = klass.flows
109
+ next if flows.nil? || flows.empty?
110
+ rescue => e
111
+ # Skip classes where flows method raises an error
112
+ warn "Warning: Could not get flows for #{klass.name}: #{e.class} - #{e.message}"
113
+ next
114
+ end
115
+
116
+ classes << klass
117
+ rescue => e
118
+ # Skip any class that causes an error during inspection
119
+ if ENV["DEBUG"]
120
+ warn "Warning: Error inspecting class #{begin
121
+ klass.name
122
+ rescue
123
+ "unknown"
124
+ end}: #{e.class} - #{e.message}"
125
+ end
126
+ next
127
+ end
128
+
129
+ classes.sort_by(&:name)
89
130
  end
90
131
 
91
- # Generate diagram file(s)
92
- begin
93
- generator = case options[:format]
94
- when "plantuml"
95
- Circulator::PlantUml.new(model_class)
96
- else
97
- Circulator::Dot.new(model_class)
132
+ # Generate diagram for a single model class
133
+ def generate_diagram_for_class(model_class, options)
134
+ begin
135
+ generator = case options[:format]
136
+ when "plantuml"
137
+ Circulator::PlantUml.new(model_class)
138
+ else
139
+ Circulator::Dot.new(model_class)
140
+ end
141
+ rescue => e
142
+ raise "Failed to initialize diagram generator for #{model_class.name}: #{e.class} - #{e.message}"
98
143
  end
99
144
 
100
145
  # Determine base output filename and extension
@@ -163,6 +208,59 @@ begin
163
208
  puts " dot -Tpng #{output_file} -o #{File.join(options[:directory], base_name)}.png"
164
209
  end
165
210
  end
211
+ end
212
+
213
+ # Collect classes to generate diagrams for
214
+ begin
215
+ if options[:all]
216
+ # Find all classes with Circulator in their ancestry
217
+ begin
218
+ classes_to_process = find_circulator_classes
219
+ rescue => e
220
+ warn "Error finding Circulator classes: #{e.class} - #{e.message}"
221
+ warn e.backtrace.first(10).join("\n")
222
+ exit 1
223
+ end
224
+
225
+ if classes_to_process.empty?
226
+ warn "No classes with Circulator flows found"
227
+ exit 1
228
+ end
229
+
230
+ puts "Found #{classes_to_process.size} class(es) with Circulator flows:"
231
+ classes_to_process.each do |klass|
232
+ puts " - #{klass.name}"
233
+ end
234
+ puts ""
235
+ else
236
+ # Try to constantize the model name
237
+ begin
238
+ model_class = Object.const_get(model_name)
239
+ rescue NameError
240
+ warn "Error: Model '#{model_name}' not found"
241
+ warn "Make sure the model is loaded in your environment"
242
+ exit 1
243
+ end
244
+
245
+ classes_to_process = [model_class]
246
+ end
247
+
248
+ # Generate diagrams for all classes in the array
249
+ classes_to_process.each do |model_class|
250
+ if classes_to_process.size > 1
251
+ puts "Generating diagram for #{model_class.name}..."
252
+ end
253
+
254
+ begin
255
+ generate_diagram_for_class(model_class, options)
256
+ rescue => e
257
+ warn "Error generating diagram for #{model_class.name}: #{e.class} - #{e.message}"
258
+ warn e.backtrace.first(5).join("\n") if ENV["DEBUG"]
259
+ next
260
+ end
261
+
262
+ puts "" if classes_to_process.size > 1
263
+ end
166
264
 
167
265
  exit 0
168
266
  rescue ArgumentError => e
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Circulator
4
- VERSION = "2.1.5"
4
+ VERSION = "2.1.6"
5
5
  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: 2.1.5
4
+ version: 2.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay