aasm-vis 0.1.4 → 0.2.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/.rubocop.yml +5 -0
- data/CHANGELOG.md +19 -0
- data/CODEOWNERS +1 -0
- data/README.md +31 -12
- data/lib/aasm/vis/tasks/generate.rake +6 -4
- data/lib/aasm/vis/version.rb +1 -1
- data/lib/aasm/vis.rb +91 -34
- metadata +4 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d7b6d25b80b1bd50f15e6ab175b28ce0b7370fce404effdc28106a866ecdc96b
|
|
4
|
+
data.tar.gz: 6b18f5d5a8c0282f30b707dce7501dfb5904cdd9d3860b34215a4d940012f206
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f90915660c6b6ec2d5010b21fcdf8d495df6d50344ddd1d9b06060b25685b9a6f432769edcae87dbb52b8d0e2f7a9314fd9ca34ea61723489372a573ac5f3871
|
|
7
|
+
data.tar.gz: ba9a2e7ca3070ce3b53b29002c2085b554afd14a1a508f809f9d9c22ab3743248de285fe390e71bffef75609fa53a82d20f91cce21052be78146d8a6fc9573af
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - 2026-06-23
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Label each state-diagram edge with the event that triggers the transition
|
|
15
|
+
(`from --> to : event_name`), so transitions leaving the same state are no
|
|
16
|
+
longer ambiguous.
|
|
17
|
+
- Limit generation to specific classes via rake task arguments, e.g.
|
|
18
|
+
`rake 'aasm_vis:generate[Job,Order]'`. With no arguments every machine is
|
|
19
|
+
still generated.
|
data/CODEOWNERS
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @kamil-gwozdz
|
data/README.md
CHANGED
|
@@ -10,7 +10,19 @@ Add `gem 'aasm-vis', group: :development` to your `Gemfile` and run `bundle`.
|
|
|
10
10
|
|
|
11
11
|
`bundle exec rake aasm_vis:generate`
|
|
12
12
|
|
|
13
|
-
To visualise the results you can use the [github cli](https://cli.github.com/): `gh gist create tmp/
|
|
13
|
+
To visualise the results you can use the [github cli](https://cli.github.com/): `gh gist create tmp/aasm-vis.md` or any other tool that can render markdown files supporting mermaid.
|
|
14
|
+
|
|
15
|
+
### Selecting models
|
|
16
|
+
|
|
17
|
+
By default every AASM state machine in the application is diagrammed. To limit
|
|
18
|
+
the output to specific classes, pass them as rake task arguments:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
bundle exec rake 'aasm_vis:generate[Job,Order]'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The quotes are required in zsh, which otherwise treats the brackets as a glob.
|
|
25
|
+
Namespaced classes must be given in full, e.g. `'aasm_vis:generate[Billing::Invoice]'`.
|
|
14
26
|
|
|
15
27
|
## Example
|
|
16
28
|
|
|
@@ -18,23 +30,31 @@ The following ruby code defines a simple state machine for a `Job` model:
|
|
|
18
30
|
```ruby
|
|
19
31
|
class Job < ApplicationRecord
|
|
20
32
|
include AASM
|
|
21
|
-
|
|
33
|
+
|
|
22
34
|
aasm :state do
|
|
23
35
|
state :created, initial: true
|
|
24
36
|
state :running
|
|
25
37
|
state :finished_successfully
|
|
26
38
|
state :finished_with_error
|
|
27
|
-
|
|
28
|
-
event :run
|
|
39
|
+
|
|
40
|
+
event :run do
|
|
29
41
|
transitions from: :created, to: :running
|
|
30
|
-
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
event :succeed do
|
|
31
45
|
transitions from: :running, to: :finished_successfully
|
|
32
46
|
end
|
|
47
|
+
|
|
48
|
+
event :error do
|
|
49
|
+
transitions from: :running, to: :finished_with_error
|
|
50
|
+
end
|
|
33
51
|
end
|
|
34
52
|
end
|
|
35
53
|
```
|
|
36
54
|
|
|
37
|
-
It will generate the following mermaid diagram
|
|
55
|
+
It will generate the following mermaid diagram. Each edge is labelled with the
|
|
56
|
+
event that triggers the transition, so transitions leaving the same state stay
|
|
57
|
+
distinguishable:
|
|
38
58
|
|
|
39
59
|
```mermaid
|
|
40
60
|
---
|
|
@@ -46,14 +66,13 @@ created : Created
|
|
|
46
66
|
running : Running
|
|
47
67
|
finished_successfully : Finished successfully
|
|
48
68
|
finished_with_error : Finished with error
|
|
49
|
-
|
|
50
|
-
[*] --> created
|
|
51
|
-
created --> running
|
|
52
|
-
running --> finished_with_error
|
|
53
|
-
running --> finished_successfully
|
|
54
69
|
|
|
55
|
-
|
|
70
|
+
created --> running : run
|
|
71
|
+
running --> finished_successfully : succeed
|
|
72
|
+
running --> finished_with_error : error
|
|
73
|
+
|
|
56
74
|
finished_successfully --> [*]
|
|
75
|
+
finished_with_error --> [*]
|
|
57
76
|
```
|
|
58
77
|
|
|
59
78
|
## Development
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class Helper
|
|
2
4
|
include AASM::Vis
|
|
3
5
|
end
|
|
4
6
|
|
|
5
7
|
namespace :aasm_vis do
|
|
6
|
-
desc
|
|
8
|
+
desc "Generate markdown file with visualisation of AASM state machines. " \
|
|
9
|
+
"Optionally limit to specific classes, e.g. aasm_vis:generate[Job,Order]."
|
|
7
10
|
|
|
8
11
|
dependencies = defined?(Rails) ? [:environment] : []
|
|
9
|
-
task generate: dependencies do
|
|
10
|
-
|
|
11
|
-
helper.generate_markdown
|
|
12
|
+
task :generate, [:only] => dependencies do |_task, args|
|
|
13
|
+
Helper.new.generate_markdown(only: args.to_a)
|
|
12
14
|
end
|
|
13
15
|
end
|
data/lib/aasm/vis/version.rb
CHANGED
data/lib/aasm/vis.rb
CHANGED
|
@@ -3,51 +3,108 @@
|
|
|
3
3
|
require_relative "vis/version"
|
|
4
4
|
|
|
5
5
|
module AASM
|
|
6
|
+
# Generates Mermaid state-diagram markdown for every AASM state machine
|
|
7
|
+
# registered in the host application. Mix into a class and call
|
|
8
|
+
# +generate_markdown+ (the rake task does this) or +build_diagrams+ to get the
|
|
9
|
+
# markdown as a string.
|
|
6
10
|
module Vis
|
|
7
|
-
require_relative
|
|
11
|
+
require_relative "vis/railtie" if defined?(Rails)
|
|
8
12
|
|
|
9
13
|
class Error < StandardError; end
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
# Writes the Mermaid markdown for the AASM state machines to +tmp/aasm-vis.md+.
|
|
16
|
+
#
|
|
17
|
+
# @param only [Array<String>, nil] class names to include; nil or empty
|
|
18
|
+
# generates every machine (the default).
|
|
19
|
+
# @return [void]
|
|
20
|
+
def generate_markdown(only: nil)
|
|
12
21
|
Rails.application.eager_load! if defined?(Rails)
|
|
13
22
|
|
|
14
|
-
|
|
23
|
+
path = File.join(Dir.pwd, "tmp", "aasm-vis.md")
|
|
24
|
+
File.write(path, build_diagrams(only: only))
|
|
25
|
+
puts "File written to: #{path}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Builds the Mermaid markdown for the AASM state machines.
|
|
29
|
+
#
|
|
30
|
+
# @param only [Array<String>, nil] class names to include; nil or empty
|
|
31
|
+
# includes every machine. Namespaced classes must be given in full
|
|
32
|
+
# (e.g. "Billing::Invoice").
|
|
33
|
+
# @return [String] concatenated ```mermaid blocks, one per state machine.
|
|
34
|
+
def build_diagrams(only: nil)
|
|
35
|
+
filter = Array(only).map(&:to_s).reject(&:empty?)
|
|
36
|
+
diagrams = []
|
|
15
37
|
|
|
16
38
|
AASM::StateMachineStore.stores.each do |klass_name, klass_store|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
event.name
|
|
22
|
-
event.default_display_name
|
|
23
|
-
|
|
24
|
-
event.transitions.each do |transition|
|
|
25
|
-
transitions << [transition.from, transition.to]
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
results << <<~TXT
|
|
30
|
-
```mermaid
|
|
31
|
-
---
|
|
32
|
-
title: #{klass}##{column}
|
|
33
|
-
---
|
|
34
|
-
stateDiagram-v2
|
|
35
|
-
|
|
36
|
-
#{klass.aasm(column).states.map { |state| "#{state.name} : #{state.default_display_name}" }.join("\n") }
|
|
37
|
-
|
|
38
|
-
#{transitions.map { |from, to| "#{from.nil? ? "[*]" : from } --> #{to}" }.join("\n") }
|
|
39
|
-
|
|
40
|
-
#{transitions.map { |_from, to| "#{to} --> [*]" if transitions.none? { |t| t[0] == to } }.reject(&:nil?).join("\n")}
|
|
41
|
-
```
|
|
42
|
-
TXT
|
|
43
|
-
end
|
|
39
|
+
next unless included?(klass_name, filter)
|
|
40
|
+
|
|
41
|
+
klass = klass_name.safe_constantize
|
|
42
|
+
klass_store.machine_names.each { |column| diagrams << diagram_for(klass, column) }
|
|
44
43
|
end
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
diagrams.join("\n\n")
|
|
46
|
+
end
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# @return [Boolean] true when filter is empty (include all) or names this class.
|
|
51
|
+
def included?(klass_name, filter)
|
|
52
|
+
filter.empty? || filter.include?(klass_name.to_s)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Builds a single ```mermaid stateDiagram-v2 block for one machine.
|
|
56
|
+
#
|
|
57
|
+
# Each transition is rendered as a labelled edge (+from --> to : event_name+)
|
|
58
|
+
# so transitions between the same pair of states triggered by different
|
|
59
|
+
# events stay distinguishable.
|
|
60
|
+
#
|
|
61
|
+
# @param klass [Class] the AASM-including class.
|
|
62
|
+
# @param column [String] the state machine name (e.g. "state").
|
|
63
|
+
# @return [String]
|
|
64
|
+
def diagram_for(klass, column)
|
|
65
|
+
machine = klass.aasm(column)
|
|
66
|
+
transitions = collect_transitions(machine)
|
|
67
|
+
|
|
68
|
+
<<~TXT
|
|
69
|
+
```mermaid
|
|
70
|
+
---
|
|
71
|
+
title: #{klass}##{column}
|
|
72
|
+
---
|
|
73
|
+
stateDiagram-v2
|
|
74
|
+
|
|
75
|
+
#{state_nodes(machine).join("\n")}
|
|
76
|
+
|
|
77
|
+
#{transition_edges(transitions).join("\n")}
|
|
78
|
+
|
|
79
|
+
#{terminal_edges(transitions).join("\n")}
|
|
80
|
+
```
|
|
81
|
+
TXT
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [Array<Array(Symbol, Symbol, Symbol)>] [from, to, event_name] tuples.
|
|
85
|
+
def collect_transitions(machine)
|
|
86
|
+
machine.events.flat_map do |event|
|
|
87
|
+
event.transitions.map { |transition| [transition.from, transition.to, event.name] }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @return [Array<String>] +state_id : Display Name+ node declarations.
|
|
92
|
+
def state_nodes(machine)
|
|
93
|
+
machine.states.map { |state| "#{state.name} : #{state.default_display_name}" }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @return [Array<String>] labelled +from --> to : event+ edges.
|
|
97
|
+
def transition_edges(transitions)
|
|
98
|
+
transitions.map { |from, to, name| "#{from.nil? ? "[*]" : from} --> #{to} : #{name}" }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# States that are never a transition source are terminal; link them to the
|
|
102
|
+
# final pseudo-state.
|
|
103
|
+
#
|
|
104
|
+
# @return [Array<String>] +state --> [*]+ edges, de-duplicated.
|
|
105
|
+
def terminal_edges(transitions)
|
|
106
|
+
transitions.map { |_from, to, _name| "#{to} --> [*]" if transitions.none? { |t| t[0] == to } }
|
|
107
|
+
.compact.uniq
|
|
51
108
|
end
|
|
52
109
|
end
|
|
53
110
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aasm-vis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kamil Gwóźdź
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 2026-06-23 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: aasm
|
|
@@ -63,6 +62,7 @@ files:
|
|
|
63
62
|
- ".rspec"
|
|
64
63
|
- ".rubocop.yml"
|
|
65
64
|
- CHANGELOG.md
|
|
65
|
+
- CODEOWNERS
|
|
66
66
|
- CODE_OF_CONDUCT.md
|
|
67
67
|
- LICENSE.txt
|
|
68
68
|
- README.md
|
|
@@ -82,7 +82,6 @@ metadata:
|
|
|
82
82
|
homepage_uri: https://github.com/kamil-gwozdz/aasm-vis
|
|
83
83
|
source_code_uri: https://github.com/kamil-gwozdz/aasm-vis
|
|
84
84
|
changelog_uri: https://github.com/kamil-gwozdz/aasm-vis/blob/main/CHANGELOG.md
|
|
85
|
-
post_install_message:
|
|
86
85
|
rdoc_options: []
|
|
87
86
|
require_paths:
|
|
88
87
|
- lib
|
|
@@ -97,8 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
97
96
|
- !ruby/object:Gem::Version
|
|
98
97
|
version: '0'
|
|
99
98
|
requirements: []
|
|
100
|
-
rubygems_version: 3.
|
|
101
|
-
signing_key:
|
|
99
|
+
rubygems_version: 3.6.6
|
|
102
100
|
specification_version: 4
|
|
103
101
|
summary: Gem for visualizing AASM state machines.
|
|
104
102
|
test_files: []
|