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 +4 -4
- data/.rubocop.yml +7 -0
- data/Gemfile +2 -0
- data/README.md +36 -5
- data/Steepfile +1 -1
- data/docs/img/models-with-lets.svg +96 -0
- data/docs/img/models.svg +94 -0
- data/lib/snapbot/diagram/dot_generator.rb +13 -9
- data/lib/snapbot/diagram/renderer.rb +7 -7
- data/lib/snapbot/diagram.rb +16 -10
- data/lib/snapbot/version.rb +1 -1
- data/sig/lib/snapbot/diagram/dot_generator.rbs +1 -1
- data/sig/lib/snapbot/diagram/renderer.rbs +4 -3
- data/sig/lib/snapbot/diagram.rbs +2 -1
- data/snapbot.gemspec +1 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd8932c76924b2ce01335b13174915694258dcd419d5c7da3fe5daa45a7e66c7
|
4
|
+
data.tar.gz: 9aa4282ef7e47c1789b742f28a977c8ea387d526ed0a8b4d524b6e205120d4f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
+

|
9
11
|
|
10
12
|
## Installation
|
11
13
|
|
12
|
-
|
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
|
-
|
23
|
+
`include Snapbot::Diagram` in your tests.
|
21
24
|
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
+

|
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 #{
|
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->Post#1 -->
|
25
|
+
<g id="edge1" class="edge">
|
26
|
+
<title>Category#1->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->Post#2 -->
|
38
|
+
<g id="edge2" class="edge">
|
39
|
+
<title>Category#1->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->Post#1 -->
|
58
|
+
<g id="edge4" class="edge">
|
59
|
+
<title>Author#1->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->Post#2 -->
|
64
|
+
<g id="edge5" class="edge">
|
65
|
+
<title>Author#1->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->Category#2 -->
|
70
|
+
<g id="edge8" class="edge">
|
71
|
+
<title>Post#2->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->Post#1 -->
|
84
|
+
<g id="edge9" class="edge">
|
85
|
+
<title>Blog#1->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->Post#2 -->
|
90
|
+
<g id="edge10" class="edge">
|
91
|
+
<title>Blog#1->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>
|
data/docs/img/models.svg
ADDED
@@ -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->Post#1 -->
|
25
|
+
<g id="edge1" class="edge">
|
26
|
+
<title>Category#1->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->Post#2 -->
|
38
|
+
<g id="edge2" class="edge">
|
39
|
+
<title>Category#1->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->Post#1 -->
|
57
|
+
<g id="edge4" class="edge">
|
58
|
+
<title>Author#1->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->Post#2 -->
|
63
|
+
<g id="edge5" class="edge">
|
64
|
+
<title>Author#1->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->Category#2 -->
|
69
|
+
<g id="edge8" class="edge">
|
70
|
+
<title>Post#2->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->Post#1 -->
|
82
|
+
<g id="edge9" class="edge">
|
83
|
+
<title>Blog#1->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->Post#2 -->
|
88
|
+
<g id="edge10" class="edge">
|
89
|
+
<title>Blog#1->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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
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"
|
83
|
+
<td align="left" port="<%= attr %>">
|
82
84
|
<%= attr %>
|
83
|
-
|
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
|
-
|
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(
|
19
|
-
FileUtils.mkdir_p(File.dirname(
|
18
|
+
FileUtils.rm(path, force: true)
|
19
|
+
FileUtils.mkdir_p(File.dirname(path))
|
20
20
|
|
21
|
-
IO.popen("dot -Tsvg -o #{
|
21
|
+
IO.popen("dot -Tsvg -o #{path}", "w") do |pipe|
|
22
22
|
pipe.puts(@dot)
|
23
23
|
end
|
24
24
|
|
25
|
-
warn "Written to #{
|
26
|
-
|
25
|
+
warn "Written to #{path}"
|
26
|
+
path
|
27
27
|
end
|
28
28
|
|
29
29
|
private
|
data/lib/snapbot/diagram.rb
CHANGED
@@ -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
|
-
|
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
|
16
|
-
warn "
|
13
|
+
unless launchy_present?
|
14
|
+
warn "Cannot open diagram – install `launchy`."
|
17
15
|
return
|
18
16
|
end
|
19
17
|
|
20
|
-
|
21
|
-
|
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
|
25
|
-
|
27
|
+
def launchy_present?
|
28
|
+
require "launchy"
|
29
|
+
true
|
30
|
+
rescue LoadError
|
31
|
+
false
|
26
32
|
end
|
27
33
|
end
|
28
34
|
end
|
data/lib/snapbot/version.rb
CHANGED
@@ -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:
|
6
|
-
|
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
|
data/sig/lib/snapbot/diagram.rbs
CHANGED
@@ -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
|
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.
|
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-
|
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
|