snapbot 0.2.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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