aasm-vis 0.1.3 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4273c8cdcd2c48874d413838a90680f0cfa0a01ea706e10f3c657e0270856f47
4
- data.tar.gz: 739e683166d0804ec22572acbc4150f4931320da30087e071b057cb8824a00cc
3
+ metadata.gz: d7b6d25b80b1bd50f15e6ab175b28ce0b7370fce404effdc28106a866ecdc96b
4
+ data.tar.gz: 6b18f5d5a8c0282f30b707dce7501dfb5904cdd9d3860b34215a4d940012f206
5
5
  SHA512:
6
- metadata.gz: 621a4e398453d5524fa12c70b42c1e0a91887da11463c3b2c70d59b5823b2cd2e8d4b2e87b554ca1d021ec7f9cea05fc3dca278abec2b9223cba8e6fdf97f0f8
7
- data.tar.gz: e50f1b9d8d56b4a24dc2e450c42791d8040122ba84bfd0ab91a44d8dd0871456033104f610e5755e3814c32a2986dc160781fe309efd817df388118a19de783d
6
+ metadata.gz: f90915660c6b6ec2d5010b21fcdf8d495df6d50344ddd1d9b06060b25685b9a6f432769edcae87dbb52b8d0e2f7a9314fd9ca34ea61723489372a573ac5f3871
7
+ data.tar.gz: ba9a2e7ca3070ce3b53b29002c2085b554afd14a1a508f809f9d9c22ab3743248de285fe390e71bffef75609fa53a82d20f91cce21052be78146d8a6fc9573af
data/.rubocop.yml CHANGED
@@ -11,3 +11,8 @@ Style/StringLiteralsInInterpolation:
11
11
 
12
12
  Layout/LineLength:
13
13
  Max: 120
14
+
15
+ # Example-heavy spec files legitimately exceed the default block length.
16
+ Metrics/BlockLength:
17
+ Exclude:
18
+ - "spec/**/*"
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/assm-vis.md` or any other tool that can render markdown files supporting mermaid.
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, after: :notify_somebody do
39
+
40
+ event :run do
29
41
  transitions from: :created, to: :running
30
- transitions from: :running, to: :finished_with_error
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
- finished_with_error --> [*]
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
data/aasm-vis.gemspec CHANGED
@@ -33,5 +33,8 @@ Gem::Specification.new do |spec|
33
33
  spec.require_paths = ["lib"]
34
34
 
35
35
  spec.add_dependency "aasm", "~> 5"
36
- # TODO: add rails or railties or rake?
36
+
37
+ # TODO make it work without Rails
38
+ spec.add_dependency "rake"
39
+ spec.add_dependency "railties"
37
40
  end
@@ -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 'Generate markdown file with visualisation of AASM state machines.'
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
- helper = Helper.new
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module AASM
4
4
  module Vis
5
- VERSION = "0.1.3"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
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 'vis/railtie' if defined?(Rails)
11
+ require_relative "vis/railtie" if defined?(Rails)
8
12
 
9
13
  class Error < StandardError; end
10
14
 
11
- def generate_markdown
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
- results = []
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
- klass_store.keys.each do |column|
18
- transitions = []
19
- klass = klass_name.safe_constantize
20
- klass.aasm(column).events.each do |event|
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
- path = File.join(Dir.pwd,'tmp', 'assm-vis.md')
47
- results = results.join("\n\n")
45
+ diagrams.join("\n\n")
46
+ end
48
47
 
49
- File.write(path, results)
50
- puts "File written to: #{path}"
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.1.3
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: 2024-07-18 00:00:00.000000000 Z
10
+ date: 2026-06-23 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: aasm
@@ -24,6 +23,34 @@ dependencies:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
25
  version: '5'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: railties
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
27
54
  description: Gem for visualising https://github.com/aasm/aasm state machines using
28
55
  markdown and https://github.com/mermaid-js/mermaid.
29
56
  email:
@@ -35,6 +62,7 @@ files:
35
62
  - ".rspec"
36
63
  - ".rubocop.yml"
37
64
  - CHANGELOG.md
65
+ - CODEOWNERS
38
66
  - CODE_OF_CONDUCT.md
39
67
  - LICENSE.txt
40
68
  - README.md
@@ -54,7 +82,6 @@ metadata:
54
82
  homepage_uri: https://github.com/kamil-gwozdz/aasm-vis
55
83
  source_code_uri: https://github.com/kamil-gwozdz/aasm-vis
56
84
  changelog_uri: https://github.com/kamil-gwozdz/aasm-vis/blob/main/CHANGELOG.md
57
- post_install_message:
58
85
  rdoc_options: []
59
86
  require_paths:
60
87
  - lib
@@ -69,8 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
96
  - !ruby/object:Gem::Version
70
97
  version: '0'
71
98
  requirements: []
72
- rubygems_version: 3.4.10
73
- signing_key:
99
+ rubygems_version: 3.6.6
74
100
  specification_version: 4
75
101
  summary: Gem for visualizing AASM state machines.
76
102
  test_files: []