snapbot 0.2.1 → 0.4.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: 7f8b1c94c13ad202a7a33eb020d8aa4c6fc66b3123a08f0f210c13a2b91598c3
4
- data.tar.gz: 54ade2525a1d3f0497c4148610488b350bacc2c2860ed7e6969d5fb237cce0f9
3
+ metadata.gz: fd8932c76924b2ce01335b13174915694258dcd419d5c7da3fe5daa45a7e66c7
4
+ data.tar.gz: 9aa4282ef7e47c1789b742f28a977c8ea387d526ed0a8b4d524b6e205120d4f1
5
5
  SHA512:
6
- metadata.gz: 7b4b99a9dd2bf1dea4174ff924eed781818f29707a89b94d3952830ecd59368fbb2632d90c3633414aa175ac03741dcfaa322698160f8e2484d1b1884cdbad5d
7
- data.tar.gz: cd76dec5c1fd06383679a1d35b06cd8cd59d17e8c9a676395efbe30065c79b1baa99cad653f6589557243a1ace625050b470b7133843d41663fdc210dfd02fc0
6
+ metadata.gz: 5c3e26e2eedb55db8876619da0c63fb79754b8c584d3ed1cf6599fbc37d8f8174af90f706bf6c5ee34e88e60a4482ab57b438613c04e426992a0f8ab80b13499
7
+ data.tar.gz: 3153f489921edecb2ca66bb7781911d0ff13e3ce563749773e649cb5a8c1c6229cc67f89cfbdcb96e17e9270cfcdc65ea2a4af7aa10e2cec8f29f6977e397357
data/.rubocop.yml CHANGED
@@ -1,9 +1,13 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.6
3
3
  SuggestExtensions: false
4
+ NewCops: enable
4
5
  Exclude:
5
6
  - Steepfile
7
+ - vendor/bundle/**/*
6
8
 
9
+ Gemspec/RequireMFA:
10
+ Enabled: false
7
11
 
8
12
  Metrics/AbcSize:
9
13
  Exclude:
@@ -18,6 +22,9 @@ Metrics/MethodLength:
18
22
  Exclude:
19
23
  - spec/support/fixture_database.rb
20
24
 
25
+ Style/DoubleNegation:
26
+ Enabled: false
27
+
21
28
  Style/StderrPuts:
22
29
  Exclude:
23
30
  - lib/snapbot/diagram/renderer.rb
data/Gemfile CHANGED
@@ -5,6 +5,8 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in snapbot.gemspec
6
6
  gemspec
7
7
 
8
+ gem "launchy", "~> 2.5.0"
9
+
8
10
  gem "rake", "~> 13.0"
9
11
 
10
12
  gem "rspec", "~> 3.0"
data/README.md CHANGED
@@ -6,10 +6,13 @@ Snapbot generates little diagrams via `save_and_open_diagram` for you to visuali
6
6
  ActiveRecord objects that you find in feature and integration tests. These are most often made by
7
7
  [FactoryBot](https://github.com/thoughtbot/factory_bot) or some other fixture-handling method, but this gem has no
8
8
  opinions on those (beyond namechecking).
9
+
10
+ ![example](docs/img/models.svg)
9
11
 
10
12
  ## Installation
11
13
 
12
- Either `gem install snapbot` or add the gem to your project's `:test` group in the gemfile:
14
+ Snapbot requires [Graphviz](https://graphviz.org/download/#executable-packages), and cannot function without it.
15
+ Install this first, then add the gem to your project's `:test` group in the gemfile:
13
16
 
14
17
  ```ruby
15
18
  group :test do
@@ -17,13 +20,19 @@ Either `gem install snapbot` or add the gem to your project's `:test` group in t
17
20
  end
18
21
  ```
19
22
 
20
- Add to your tests:
23
+ `include Snapbot::Diagram` in your tests.
21
24
 
22
- ```
23
- include Snapbot::Diagram
25
+ For RSpec, you may prefer to put something like
26
+
27
+ ```ruby
28
+ RSpec.config do |config|
29
+ config.include Snapbot::Diagram, type: :feature
30
+ end
24
31
  ```
25
32
 
26
- Use:
33
+ in your `spec/rails_helper` to have it mixed in automatically (to features, in this case).
34
+
35
+ ## Usage example
27
36
 
28
37
  ```
29
38
  blog = Blog.create(title: 'My blog')
@@ -32,6 +41,28 @@ Use:
32
41
  save_and_open_diagram
33
42
  ```
34
43
 
44
+ ## RSpec integration
45
+
46
+ Sometimes it's useful to see the models you created as preconditions vs the ones your code has created in response to
47
+ those. If you use RSpec's [`let` construct](https://relishapp.com/rspec/rspec-core/v/3-11/docs/helper-methods/let-and-let)
48
+ then Snapbot will match these automatically for you and annotate the diagram. For example:
49
+
50
+ ```ruby
51
+ include Snapbot::Diagram
52
+ describe "the categorised post creator" do
53
+ let(:blog) { create :blog }
54
+ let(:author) { create :author }
55
+
56
+ it "creates posts automatically, categorised" do
57
+ CategorisedPostCreator.new(blog, author).run
58
+ save_and_open_diagram
59
+ expect(Post.count).to eql(2)
60
+ end
61
+ end
62
+ ```
63
+
64
+ ![annotated diagram](docs/img/models-with-lets.svg)
65
+
35
66
  ## Why?
36
67
 
37
68
  Sometimes, you need to create a few ActiveRecord objects for your test suite. Sometimes, there will be a little cluster
data/Steepfile CHANGED
@@ -26,7 +26,7 @@ target :lib do
26
26
  # lib/snapbot/diagram/renderer.rb:21:58: [error] The method cannot be called with a block
27
27
  # │ Diagnostic ID: Ruby::UnexpectedBlockGiven
28
28
  # │
29
- # └ IO.popen("dot -Tsvg -o #{OUTPUT_FILENAME}", "w+") do |pipe|
29
+ # └ IO.popen("dot -Tsvg -o #{DEFAULT_OUTPUT_FILENAME}", "w+") do |pipe|
30
30
  # ~~~~~~~~~
31
31
  #
32
32
  # This disables that ^^ but probably a bit too much. Can we restrict to renderer.rb?
@@ -0,0 +1,96 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
3
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+ <!-- Generated by graphviz version 3.0.0 (20220226.1711)
5
+ -->
6
+ <!-- Title: g Pages: 1 -->
7
+ <svg width="322pt" height="247pt"
8
+ viewBox="0.00 0.00 322.10 246.60" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
9
+ <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(28.8 217.8)">
10
+ <title>g</title>
11
+ <polygon fill="white" stroke="transparent" points="-28.8,28.8 -28.8,-217.8 293.3,-217.8 293.3,28.8 -28.8,28.8"/>
12
+ <!-- Category#1 -->
13
+ <g id="node1" class="node">
14
+ <title>Category#1</title>
15
+ <path fill="none" stroke="black" d="M12,-149.5C12,-149.5 57,-149.5 57,-149.5 63,-149.5 69,-155.5 69,-161.5 69,-161.5 69,-173.5 69,-173.5 69,-179.5 63,-185.5 57,-185.5 57,-185.5 12,-185.5 12,-185.5 6,-185.5 0,-179.5 0,-173.5 0,-173.5 0,-161.5 0,-161.5 0,-155.5 6,-149.5 12,-149.5"/>
16
+ <text text-anchor="start" x="9.5" y="-164.5" font-family="ArialMT" font-size="10.00">Category#1</text>
17
+ </g>
18
+ <!-- Post#1 -->
19
+ <g id="node4" class="node">
20
+ <title>Post#1</title>
21
+ <path fill="none" stroke="black" d="M77.5,-73.5C77.5,-73.5 107.5,-73.5 107.5,-73.5 113.5,-73.5 119.5,-79.5 119.5,-85.5 119.5,-85.5 119.5,-97.5 119.5,-97.5 119.5,-103.5 113.5,-109.5 107.5,-109.5 107.5,-109.5 77.5,-109.5 77.5,-109.5 71.5,-109.5 65.5,-103.5 65.5,-97.5 65.5,-97.5 65.5,-85.5 65.5,-85.5 65.5,-79.5 71.5,-73.5 77.5,-73.5"/>
22
+ <text text-anchor="start" x="77.5" y="-88.5" font-family="ArialMT" font-size="10.00">Post#1</text>
23
+ </g>
24
+ <!-- Category#1&#45;&gt;Post#1 -->
25
+ <g id="edge1" class="edge">
26
+ <title>Category#1&#45;&gt;Post#1</title>
27
+ <path fill="none" stroke="black" d="M53.51,-142.25C59.92,-134.07 67.07,-124.94 73.48,-116.76"/>
28
+ <polygon fill="black" stroke="black" points="51.02,-140.31 47.95,-149.34 55.98,-144.2 51.02,-140.31"/>
29
+ <polygon fill="black" stroke="black" points="76.05,-118.6 79.12,-109.57 71.09,-114.71 76.05,-118.6"/>
30
+ </g>
31
+ <!-- Post#2 -->
32
+ <g id="node5" class="node">
33
+ <title>Post#2</title>
34
+ <path fill="none" stroke="black" d="M160.5,-73.5C160.5,-73.5 190.5,-73.5 190.5,-73.5 196.5,-73.5 202.5,-79.5 202.5,-85.5 202.5,-85.5 202.5,-97.5 202.5,-97.5 202.5,-103.5 196.5,-109.5 190.5,-109.5 190.5,-109.5 160.5,-109.5 160.5,-109.5 154.5,-109.5 148.5,-103.5 148.5,-97.5 148.5,-97.5 148.5,-85.5 148.5,-85.5 148.5,-79.5 154.5,-73.5 160.5,-73.5"/>
35
+ <text text-anchor="start" x="160.5" y="-88.5" font-family="ArialMT" font-size="10.00">Post#2</text>
36
+ </g>
37
+ <!-- Category#1&#45;&gt;Post#2 -->
38
+ <g id="edge2" class="edge">
39
+ <title>Category#1&#45;&gt;Post#2</title>
40
+ <path fill="none" stroke="black" d="M75.24,-145.12C95.75,-134.36 120.36,-121.44 140.07,-111.1"/>
41
+ <polygon fill="black" stroke="black" points="73.71,-142.37 67.2,-149.34 76.64,-147.94 73.71,-142.37"/>
42
+ <polygon fill="black" stroke="black" points="141.68,-113.81 148.19,-106.83 138.76,-108.23 141.68,-113.81"/>
43
+ </g>
44
+ <!-- Category#2 -->
45
+ <g id="node2" class="node">
46
+ <title>Category#2</title>
47
+ <path fill="none" stroke="black" d="M153,-0.5C153,-0.5 198,-0.5 198,-0.5 204,-0.5 210,-6.5 210,-12.5 210,-12.5 210,-24.5 210,-24.5 210,-30.5 204,-36.5 198,-36.5 198,-36.5 153,-36.5 153,-36.5 147,-36.5 141,-30.5 141,-24.5 141,-24.5 141,-12.5 141,-12.5 141,-6.5 147,-0.5 153,-0.5"/>
48
+ <text text-anchor="start" x="150.5" y="-15.5" font-family="ArialMT" font-size="10.00">Category#2</text>
49
+ </g>
50
+ <!-- Author#1 -->
51
+ <g id="node3" class="node">
52
+ <title>Author#1</title>
53
+ <path fill="none" stroke="black" d="M110,-146.5C110,-146.5 159,-146.5 159,-146.5 165,-146.5 171,-152.5 171,-158.5 171,-158.5 171,-176.5 171,-176.5 171,-182.5 165,-188.5 159,-188.5 159,-188.5 110,-188.5 110,-188.5 104,-188.5 98,-182.5 98,-176.5 98,-176.5 98,-158.5 98,-158.5 98,-152.5 104,-146.5 110,-146.5"/>
54
+ <text text-anchor="start" x="107.5" y="-174.1" font-family="Monaco" font-size="8.00">let(:author)</text>
55
+ <text text-anchor="start" x="115" y="-157.5" font-family="ArialMT" font-size="10.00">Author#1</text>
56
+ </g>
57
+ <!-- Author#1&#45;&gt;Post#1 -->
58
+ <g id="edge4" class="edge">
59
+ <title>Author#1&#45;&gt;Post#1</title>
60
+ <path fill="none" stroke="black" d="M118.56,-138.42C113.08,-128.76 107.1,-118.22 102.25,-109.69"/>
61
+ <polygon fill="black" stroke="black" points="115.84,-140.01 123.02,-146.28 121.32,-136.9 115.84,-140.01"/>
62
+ </g>
63
+ <!-- Author#1&#45;&gt;Post#2 -->
64
+ <g id="edge5" class="edge">
65
+ <title>Author#1&#45;&gt;Post#2</title>
66
+ <path fill="none" stroke="black" d="M150.22,-138.13C155.53,-128.55 161.3,-118.14 165.98,-109.69"/>
67
+ <polygon fill="black" stroke="black" points="147.31,-136.88 145.7,-146.28 152.82,-139.93 147.31,-136.88"/>
68
+ </g>
69
+ <!-- Post#2&#45;&gt;Category#2 -->
70
+ <g id="edge8" class="edge">
71
+ <title>Post#2&#45;&gt;Category#2</title>
72
+ <path fill="none" stroke="black" d="M175.5,-64.13C175.5,-58.07 175.5,-51.64 175.5,-45.59"/>
73
+ <polygon fill="black" stroke="black" points="172.35,-64.31 175.5,-73.31 178.65,-64.31 172.35,-64.31"/>
74
+ <polygon fill="black" stroke="black" points="178.65,-45.53 175.5,-36.53 172.35,-45.53 178.65,-45.53"/>
75
+ </g>
76
+ <!-- Blog#1 -->
77
+ <g id="node6" class="node">
78
+ <title>Blog#1</title>
79
+ <path fill="none" stroke="black" d="M212.5,-146.5C212.5,-146.5 252.5,-146.5 252.5,-146.5 258.5,-146.5 264.5,-152.5 264.5,-158.5 264.5,-158.5 264.5,-176.5 264.5,-176.5 264.5,-182.5 258.5,-188.5 252.5,-188.5 252.5,-188.5 212.5,-188.5 212.5,-188.5 206.5,-188.5 200.5,-182.5 200.5,-176.5 200.5,-176.5 200.5,-158.5 200.5,-158.5 200.5,-152.5 206.5,-146.5 212.5,-146.5"/>
80
+ <text text-anchor="start" x="209.5" y="-174.1" font-family="Monaco" font-size="8.00">let(:blog)</text>
81
+ <text text-anchor="start" x="217" y="-157.5" font-family="ArialMT" font-size="10.00">Blog#1</text>
82
+ </g>
83
+ <!-- Blog#1&#45;&gt;Post#1 -->
84
+ <g id="edge9" class="edge">
85
+ <title>Blog#1&#45;&gt;Post#1</title>
86
+ <path fill="none" stroke="black" d="M192.4,-145.3C169.12,-133 140.41,-117.83 119.75,-106.91"/>
87
+ <polygon fill="black" stroke="black" points="190.95,-148.1 200.38,-149.52 193.9,-142.53 190.95,-148.1"/>
88
+ </g>
89
+ <!-- Blog#1&#45;&gt;Post#2 -->
90
+ <g id="edge10" class="edge">
91
+ <title>Blog#1&#45;&gt;Post#2</title>
92
+ <path fill="none" stroke="black" d="M211.32,-139C203.75,-129.18 195.45,-118.39 188.74,-109.69"/>
93
+ <polygon fill="black" stroke="black" points="208.94,-141.07 216.92,-146.28 213.93,-137.23 208.94,-141.07"/>
94
+ </g>
95
+ </g>
96
+ </svg>
@@ -0,0 +1,94 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
3
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+ <!-- Generated by graphviz version 3.0.0 (20220226.1711)
5
+ -->
6
+ <!-- Title: g Pages: 1 -->
7
+ <svg width="297pt" height="241pt"
8
+ viewBox="0.00 0.00 297.10 240.60" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
9
+ <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(28.8 211.8)">
10
+ <title>g</title>
11
+ <polygon fill="white" stroke="transparent" points="-28.8,28.8 -28.8,-211.8 268.3,-211.8 268.3,28.8 -28.8,28.8"/>
12
+ <!-- Category#1 -->
13
+ <g id="node1" class="node">
14
+ <title>Category#1</title>
15
+ <path fill="none" stroke="black" d="M12,-146.5C12,-146.5 57,-146.5 57,-146.5 63,-146.5 69,-152.5 69,-158.5 69,-158.5 69,-170.5 69,-170.5 69,-176.5 63,-182.5 57,-182.5 57,-182.5 12,-182.5 12,-182.5 6,-182.5 0,-176.5 0,-170.5 0,-170.5 0,-158.5 0,-158.5 0,-152.5 6,-146.5 12,-146.5"/>
16
+ <text text-anchor="start" x="9.5" y="-161.5" font-family="ArialMT" font-size="10.00">Category#1</text>
17
+ </g>
18
+ <!-- Post#1 -->
19
+ <g id="node4" class="node">
20
+ <title>Post#1</title>
21
+ <path fill="none" stroke="black" d="M70.5,-73.5C70.5,-73.5 100.5,-73.5 100.5,-73.5 106.5,-73.5 112.5,-79.5 112.5,-85.5 112.5,-85.5 112.5,-97.5 112.5,-97.5 112.5,-103.5 106.5,-109.5 100.5,-109.5 100.5,-109.5 70.5,-109.5 70.5,-109.5 64.5,-109.5 58.5,-103.5 58.5,-97.5 58.5,-97.5 58.5,-85.5 58.5,-85.5 58.5,-79.5 64.5,-73.5 70.5,-73.5"/>
22
+ <text text-anchor="start" x="70.5" y="-88.5" font-family="ArialMT" font-size="10.00">Post#1</text>
23
+ </g>
24
+ <!-- Category#1&#45;&gt;Post#1 -->
25
+ <g id="edge1" class="edge">
26
+ <title>Category#1&#45;&gt;Post#1</title>
27
+ <path fill="none" stroke="black" d="M52.19,-138.87C57.3,-131.76 62.87,-124 67.97,-116.91"/>
28
+ <polygon fill="black" stroke="black" points="49.54,-137.17 46.85,-146.31 54.65,-140.84 49.54,-137.17"/>
29
+ <polygon fill="black" stroke="black" points="70.58,-118.68 73.27,-109.53 65.46,-115 70.58,-118.68"/>
30
+ </g>
31
+ <!-- Post#2 -->
32
+ <g id="node5" class="node">
33
+ <title>Post#2</title>
34
+ <path fill="none" stroke="black" d="M153.5,-73.5C153.5,-73.5 183.5,-73.5 183.5,-73.5 189.5,-73.5 195.5,-79.5 195.5,-85.5 195.5,-85.5 195.5,-97.5 195.5,-97.5 195.5,-103.5 189.5,-109.5 183.5,-109.5 183.5,-109.5 153.5,-109.5 153.5,-109.5 147.5,-109.5 141.5,-103.5 141.5,-97.5 141.5,-97.5 141.5,-85.5 141.5,-85.5 141.5,-79.5 147.5,-73.5 153.5,-73.5"/>
35
+ <text text-anchor="start" x="153.5" y="-88.5" font-family="ArialMT" font-size="10.00">Post#2</text>
36
+ </g>
37
+ <!-- Category#1&#45;&gt;Post#2 -->
38
+ <g id="edge2" class="edge">
39
+ <title>Category#1&#45;&gt;Post#2</title>
40
+ <path fill="none" stroke="black" d="M74.6,-142.26C93.24,-132.38 115.22,-120.73 133.22,-111.19"/>
41
+ <polygon fill="black" stroke="black" points="73.07,-139.5 66.6,-146.49 76.02,-145.06 73.07,-139.5"/>
42
+ <polygon fill="black" stroke="black" points="134.71,-113.97 141.19,-106.97 131.76,-108.4 134.71,-113.97"/>
43
+ </g>
44
+ <!-- Category#2 -->
45
+ <g id="node2" class="node">
46
+ <title>Category#2</title>
47
+ <path fill="none" stroke="black" d="M146,-0.5C146,-0.5 191,-0.5 191,-0.5 197,-0.5 203,-6.5 203,-12.5 203,-12.5 203,-24.5 203,-24.5 203,-30.5 197,-36.5 191,-36.5 191,-36.5 146,-36.5 146,-36.5 140,-36.5 134,-30.5 134,-24.5 134,-24.5 134,-12.5 134,-12.5 134,-6.5 140,-0.5 146,-0.5"/>
48
+ <text text-anchor="start" x="143.5" y="-15.5" font-family="ArialMT" font-size="10.00">Category#2</text>
49
+ </g>
50
+ <!-- Author#1 -->
51
+ <g id="node3" class="node">
52
+ <title>Author#1</title>
53
+ <path fill="none" stroke="black" d="M110.5,-146.5C110.5,-146.5 144.5,-146.5 144.5,-146.5 150.5,-146.5 156.5,-152.5 156.5,-158.5 156.5,-158.5 156.5,-170.5 156.5,-170.5 156.5,-176.5 150.5,-182.5 144.5,-182.5 144.5,-182.5 110.5,-182.5 110.5,-182.5 104.5,-182.5 98.5,-176.5 98.5,-170.5 98.5,-170.5 98.5,-158.5 98.5,-158.5 98.5,-152.5 104.5,-146.5 110.5,-146.5"/>
54
+ <text text-anchor="start" x="107.5" y="-161.5" font-family="ArialMT" font-size="10.00">Author#1</text>
55
+ </g>
56
+ <!-- Author#1&#45;&gt;Post#1 -->
57
+ <g id="edge4" class="edge">
58
+ <title>Author#1&#45;&gt;Post#1</title>
59
+ <path fill="none" stroke="black" d="M112.59,-138.29C106.95,-128.76 100.67,-118.14 95.57,-109.53"/>
60
+ <polygon fill="black" stroke="black" points="110.04,-140.17 117.33,-146.31 115.46,-136.96 110.04,-140.17"/>
61
+ </g>
62
+ <!-- Author#1&#45;&gt;Post#2 -->
63
+ <g id="edge5" class="edge">
64
+ <title>Author#1&#45;&gt;Post#2</title>
65
+ <path fill="none" stroke="black" d="M142.06,-138.29C147.56,-128.76 153.69,-118.14 158.67,-109.53"/>
66
+ <polygon fill="black" stroke="black" points="139.2,-136.94 137.42,-146.31 144.65,-140.09 139.2,-136.94"/>
67
+ </g>
68
+ <!-- Post#2&#45;&gt;Category#2 -->
69
+ <g id="edge8" class="edge">
70
+ <title>Post#2&#45;&gt;Category#2</title>
71
+ <path fill="none" stroke="black" d="M168.5,-64.13C168.5,-58.07 168.5,-51.64 168.5,-45.59"/>
72
+ <polygon fill="black" stroke="black" points="165.35,-64.31 168.5,-73.31 171.65,-64.31 165.35,-64.31"/>
73
+ <polygon fill="black" stroke="black" points="171.65,-45.53 168.5,-36.53 165.35,-45.53 171.65,-45.53"/>
74
+ </g>
75
+ <!-- Blog#1 -->
76
+ <g id="node6" class="node">
77
+ <title>Blog#1</title>
78
+ <path fill="none" stroke="black" d="M197.5,-146.5C197.5,-146.5 227.5,-146.5 227.5,-146.5 233.5,-146.5 239.5,-152.5 239.5,-158.5 239.5,-158.5 239.5,-170.5 239.5,-170.5 239.5,-176.5 233.5,-182.5 227.5,-182.5 227.5,-182.5 197.5,-182.5 197.5,-182.5 191.5,-182.5 185.5,-176.5 185.5,-170.5 185.5,-170.5 185.5,-158.5 185.5,-158.5 185.5,-152.5 191.5,-146.5 197.5,-146.5"/>
79
+ <text text-anchor="start" x="197.5" y="-161.5" font-family="ArialMT" font-size="10.00">Blog#1</text>
80
+ </g>
81
+ <!-- Blog#1&#45;&gt;Post#1 -->
82
+ <g id="edge9" class="edge">
83
+ <title>Blog#1&#45;&gt;Post#1</title>
84
+ <path fill="none" stroke="black" d="M177.14,-143.73C156.75,-132.33 131.52,-118.23 112.7,-107.71"/>
85
+ <polygon fill="black" stroke="black" points="175.86,-146.63 185.26,-148.27 178.94,-141.13 175.86,-146.63"/>
86
+ </g>
87
+ <!-- Blog#1&#45;&gt;Post#2 -->
88
+ <g id="edge10" class="edge">
89
+ <title>Blog#1&#45;&gt;Post#2</title>
90
+ <path fill="none" stroke="black" d="M197.06,-138.58C191.1,-128.97 184.44,-118.23 179.05,-109.53"/>
91
+ <polygon fill="black" stroke="black" points="194.43,-140.32 201.85,-146.31 199.79,-137 194.43,-140.32"/>
92
+ </g>
93
+ </g>
94
+ </svg>
@@ -12,15 +12,17 @@ module Snapbot
12
12
  # Get a visual handle on what small constellations of objects we're creating
13
13
  # in specs
14
14
  class DotGenerator
15
- def initialize(label: "g", attrs: false, ignore_lets: %i[])
15
+ def initialize(label: "g", attrs: false, ignore_lets: %i[], rspec: false)
16
16
  @label = label
17
17
  @options = { attrs: attrs }
18
18
  @ignore_lets = ignore_lets
19
19
 
20
- return unless defined?(::RSpec)
21
-
22
- example = binding.of_caller(1).eval("self")
23
- collect_lets(example)
20
+ @lets_by_value = if rspec
21
+ example = binding.of_caller(1).eval("self")
22
+ collect_lets(example)
23
+ else
24
+ {}
25
+ end
24
26
  end
25
27
 
26
28
  def dot
@@ -37,7 +39,7 @@ module Snapbot
37
39
  end
38
40
 
39
41
  def collect_lets(example)
40
- @lets_by_value = RSpec::Lets.new(example).collect.each_with_object({}) do |sym, lets_by_value|
42
+ RSpec::Lets.new(example).collect.each_with_object({}) do |sym, lets_by_value|
41
43
  value = example.send(sym) unless @ignore_lets.include?(sym)
42
44
  lets_by_value[value] = sym if value.is_a?(reflector.base_activerecord_class)
43
45
  end
@@ -69,7 +71,7 @@ module Snapbot
69
71
  label=<
70
72
  <table border="0" cellborder="0">
71
73
  <%- if @lets_by_value[instance] -%>
72
- <tr><td fontsize="8"><font face="Monaco" point-size="8">let(:<%= @lets_by_value[instance] %>)</font></td></tr>
74
+ <tr><td><font face="Monaco,Courier,monospace" point-size="8">let(:<%= @lets_by_value[instance] %>)</font></td></tr>
73
75
  <%- end -%>
74
76
  <tr><td><%= instance_name(instance) %></td></tr>
75
77
  </table>
@@ -78,9 +80,11 @@ module Snapbot
78
80
  <table border="0" cellborder="0">
79
81
  <%- attributes(instance).each_pair do |attr, value| -%>
80
82
  <tr>
81
- <td align="left" width="200" port="<%= attr %>">
83
+ <td align="left" port="<%= attr %>">
82
84
  <%= attr %>
83
- <font face="Arial BoldMT" color="grey60"><%= value.inspect %></font>
85
+ </td>
86
+ <td align="left">
87
+ <font color="grey50"><%= value.inspect %></font>
84
88
  </td>
85
89
  </tr>
86
90
  <%- end -%>
@@ -7,23 +7,23 @@ module Snapbot
7
7
  # Render some DOT via Graphviz dot command line
8
8
  class Renderer
9
9
  INSTALL_GRAPHVIZ_URL = "https://graphviz.org/download/#executable-packages"
10
- OUTPUT_FILENAME = "tmp/models.svg"
10
+ DEFAULT_OUTPUT_FILENAME = "tmp/models.svg"
11
11
 
12
12
  def initialize(dot)
13
13
  @dot = dot
14
14
  end
15
15
 
16
- def save
16
+ def save(path = DEFAULT_OUTPUT_FILENAME)
17
17
  ensure_graphviz
18
- FileUtils.rm(OUTPUT_FILENAME, force: true)
19
- FileUtils.mkdir_p(File.dirname(OUTPUT_FILENAME))
18
+ FileUtils.rm(path, force: true)
19
+ FileUtils.mkdir_p(File.dirname(path))
20
20
 
21
- IO.popen("dot -Tsvg -o #{OUTPUT_FILENAME}", "w") do |pipe|
21
+ IO.popen("dot -Tsvg -o #{path}", "w") do |pipe|
22
22
  pipe.puts(@dot)
23
23
  end
24
24
 
25
- warn "Written to #{OUTPUT_FILENAME}"
26
- OUTPUT_FILENAME
25
+ warn "Written to #{path}"
26
+ path
27
27
  end
28
28
 
29
29
  private
@@ -2,27 +2,33 @@
2
2
 
3
3
  require "snapbot/diagram/dot_generator"
4
4
  require "snapbot/diagram/renderer"
5
- require "open3"
6
5
 
7
6
  module Snapbot
8
7
  # Print the small constellation of objects in your integration test and how they relate.
9
8
  # Requires Graphviz. Optimised for Mac. YMMV.
10
9
  module Diagram
11
- def save_and_open_diagram(**args)
12
- dot = DotGenerator.new(**args).dot
13
- filename = Renderer.new(dot).save
10
+ def save_and_open_diagram(path = Renderer::DEFAULT_OUTPUT_FILENAME, **args)
11
+ filename = save_diagram(path, **args)
14
12
 
15
- unless open_command.present?
16
- warn "No `open` command available. File saved to #{filename}"
13
+ unless launchy_present?
14
+ warn "Cannot open diagram install `launchy`."
17
15
  return
18
16
  end
19
17
 
20
- _stdout, stderr, status = Open3.capture3("#{open_command} #{filename}")
21
- raise stderr unless status.exitstatus.zero?
18
+ Launchy.open(filename)
19
+ end
20
+
21
+ def save_diagram(path = Renderer::DEFAULT_OUTPUT_FILENAME, **args)
22
+ args.reverse_merge!(rspec: !!defined?(RSpec))
23
+ dot = DotGenerator.new(**args).dot
24
+ Renderer.new(dot).save(path)
22
25
  end
23
26
 
24
- def open_command
25
- `which open`.chomp
27
+ def launchy_present?
28
+ require "launchy"
29
+ true
30
+ rescue LoadError
31
+ false
26
32
  end
27
33
  end
28
34
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Snapbot
4
- VERSION = "0.2.1"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -3,7 +3,7 @@ module Snapbot
3
3
  # Get a visual handle on what small constellations of objects we're creating
4
4
  # in specs
5
5
  class DotGenerator
6
- def initialize: (?label: ::String, ?attrs: bool, ?ignore_lets: Array[Symbol]) -> void
6
+ def initialize: (?label: ::String, ?attrs: bool, ?ignore_lets: Array[Symbol], ?rspec: bool) -> void
7
7
  def dot: () -> ::String
8
8
 
9
9
  @label: String
@@ -2,18 +2,19 @@ module Snapbot
2
2
  module Diagram
3
3
  # Render some DOT via Graphviz dot command line
4
4
  class Renderer
5
- INSTALL_GRAPHVIZ_URL: "https://graphviz.org/download/#executable-packages"
6
- OUTPUT_FILENAME: "tmp/models.svg"
5
+ INSTALL_GRAPHVIZ_URL: String
6
+ DEFAULT_OUTPUT_FILENAME: String
7
7
 
8
8
  @dot: String
9
9
 
10
10
  def initialize: (String dot) -> void
11
- def save: () -> String
11
+ def save: (?String path) -> String
12
12
 
13
13
  private
14
14
 
15
15
  def graphviz_executable: () -> ::String
16
16
  def ensure_graphviz: () -> void
17
+ def launchy_present?: () -> bool
17
18
  end
18
19
  end
19
20
  end
@@ -2,7 +2,8 @@ module Snapbot
2
2
  # Print the small constellation of objects in your integration test and how they relate.
3
3
  # Requires Graphviz. Optimised for Mac. YMMV.
4
4
  module Diagram
5
- def save_and_open_diagram: (**untyped args) -> (nil | untyped)
5
+ def save_diagram: (?String path, **untyped args) -> (nil | untyped)
6
+ def save_and_open_diagram: (?String path, **untyped args) -> (nil | untyped)
6
7
 
7
8
  def open_command: () -> untyped
8
9
  end
data/snapbot.gemspec CHANGED
@@ -35,6 +35,7 @@ Gem::Specification.new do |spec|
35
35
  spec.add_runtime_dependency "activerecord", version_string
36
36
  spec.add_runtime_dependency "activesupport", version_string
37
37
 
38
+ spec.add_development_dependency "launchy"
38
39
  spec.add_development_dependency "rbs"
39
40
  spec.add_development_dependency "sqlite3"
40
41
  spec.add_development_dependency "steep"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snapbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Russell Garner
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-09 00:00:00.000000000 Z
11
+ date: 2022-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: binding_of_caller
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '6.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: launchy
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rbs
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -124,6 +138,8 @@ files:
124
138
  - README.md
125
139
  - Rakefile
126
140
  - Steepfile
141
+ - docs/img/models-with-lets.svg
142
+ - docs/img/models.svg
127
143
  - lib/snapbot.rb
128
144
  - lib/snapbot/diagram.rb
129
145
  - lib/snapbot/diagram/dot_generator.rb