tapping_device 0.5.3 → 0.6.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/.github/workflows/ruby.yml +14 -4
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +70 -1
- data/Gemfile +11 -0
- data/Makefile +3 -0
- data/README.md +100 -133
- data/lib/tapping_device.rb +18 -12
- data/lib/tapping_device/configurable.rb +2 -0
- data/lib/tapping_device/method_hijacker.rb +4 -0
- data/lib/tapping_device/output.rb +6 -7
- data/lib/tapping_device/output/{payload.rb → payload_wrapper.rb} +46 -35
- data/lib/tapping_device/output/writer.rb +5 -3
- data/lib/tapping_device/payload.rb +23 -13
- data/lib/tapping_device/trackable.rb +48 -18
- data/lib/tapping_device/trackers/initialization_tracker.rb +30 -4
- data/lib/tapping_device/trackers/mutation_tracker.rb +3 -4
- data/lib/tapping_device/version.rb +1 -1
- data/tapping_device.gemspec +1 -7
- metadata +9 -25
- data/Gemfile.lock +0 -74
- data/lib/tapping_device/output/file_writer.rb +0 -21
- data/lib/tapping_device/output/stdout_writer.rb +0 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8adc4b6842ade7721eafaf72e9b94c8a6a852c1f9f9beebdf2556b20cef6d4a
|
|
4
|
+
data.tar.gz: ab22b319f6468837e92f851f00f863cf732dd3c0cc73e286d5f9569d00c34db6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 992f31b79a3c0a8abc8471ae643f02586cf76d505da493cfe66b37338781251c3dfdba43a158776431c93a342e8b9a02baff78118b71c679aba2f9114c65077c
|
|
7
|
+
data.tar.gz: cad971d539041804a9860225f17ae23754dc46ea6fa5b7dc15d674e5189c081dd4f6f2602eeea9c27e616fb1b959126fe452ef5f641e249d73392782b17a3c2c
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
name: Ruby
|
|
2
2
|
|
|
3
|
-
on:
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- master
|
|
8
|
+
pull_request:
|
|
4
9
|
|
|
5
10
|
jobs:
|
|
6
11
|
test:
|
|
@@ -8,9 +13,12 @@ jobs:
|
|
|
8
13
|
runs-on: ${{ matrix.os }}
|
|
9
14
|
strategy:
|
|
10
15
|
matrix:
|
|
11
|
-
rails_version: ['5.2', '6']
|
|
12
|
-
ruby_version: ['2.
|
|
16
|
+
rails_version: ['5.2', '6.0.0', '6.1.0']
|
|
17
|
+
ruby_version: ['2.7', '3.0']
|
|
13
18
|
os: [ubuntu-latest]
|
|
19
|
+
exclude:
|
|
20
|
+
- ruby_version: '3.0'
|
|
21
|
+
rails_version: '5.2'
|
|
14
22
|
steps:
|
|
15
23
|
- uses: actions/checkout@v1
|
|
16
24
|
|
|
@@ -34,7 +42,9 @@ jobs:
|
|
|
34
42
|
bundle install --jobs 4 --retry 3
|
|
35
43
|
|
|
36
44
|
- name: Run test with Rails ${{ matrix.rails_version }}
|
|
37
|
-
|
|
45
|
+
env:
|
|
46
|
+
RAILS_VERSION: ${{ matrix.rails_version }}
|
|
47
|
+
run: make test
|
|
38
48
|
|
|
39
49
|
- name: Publish Test Coverage
|
|
40
50
|
uses: paambaati/codeclimate-action@v2.6.0
|
data/.gitignore
CHANGED
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3.0.1
|
data/CHANGELOG.md
CHANGED
|
@@ -2,15 +2,84 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased](https://github.com/st0012/tapping_device/tree/HEAD)
|
|
4
4
|
|
|
5
|
-
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.
|
|
5
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.7...HEAD)
|
|
6
|
+
|
|
7
|
+
**Implemented enhancements:**
|
|
8
|
+
|
|
9
|
+
- Support Ruby 3.0 [\#71](https://github.com/st0012/tapping_device/pull/71) ([st0012](https://github.com/st0012))
|
|
10
|
+
|
|
11
|
+
**Merged pull requests:**
|
|
12
|
+
|
|
13
|
+
- Drop activerecord requirement [\#73](https://github.com/st0012/tapping_device/pull/73) ([st0012](https://github.com/st0012))
|
|
14
|
+
- Improve file-writing tests [\#72](https://github.com/st0012/tapping_device/pull/72) ([st0012](https://github.com/st0012))
|
|
15
|
+
- Simplify output logic with Ruby' Logger class [\#70](https://github.com/st0012/tapping_device/pull/70) ([st0012](https://github.com/st0012))
|
|
16
|
+
- Refactor Payload classes [\#68](https://github.com/st0012/tapping_device/pull/68) ([st0012](https://github.com/st0012))
|
|
17
|
+
|
|
18
|
+
## [v0.5.7](https://github.com/st0012/tapping_device/tree/v0.5.7) (2020-09-09)
|
|
19
|
+
|
|
20
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.6...v0.5.7)
|
|
21
|
+
|
|
22
|
+
**Closed issues:**
|
|
23
|
+
|
|
24
|
+
- Support tag option [\#64](https://github.com/st0012/tapping_device/issues/64)
|
|
25
|
+
|
|
26
|
+
**Merged pull requests:**
|
|
27
|
+
|
|
28
|
+
- Use pastel to replace handmade colorizing logic [\#66](https://github.com/st0012/tapping_device/pull/66) ([st0012](https://github.com/st0012))
|
|
29
|
+
- Add tag option [\#65](https://github.com/st0012/tapping_device/pull/65) ([st0012](https://github.com/st0012))
|
|
30
|
+
|
|
31
|
+
## [v0.5.6](https://github.com/st0012/tapping_device/tree/v0.5.6) (2020-07-17)
|
|
32
|
+
|
|
33
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.5...v0.5.6)
|
|
34
|
+
|
|
35
|
+
## [v0.5.5](https://github.com/st0012/tapping_device/tree/v0.5.5) (2020-07-16)
|
|
36
|
+
|
|
37
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.4...v0.5.5)
|
|
38
|
+
|
|
39
|
+
**Fixed bugs:**
|
|
40
|
+
|
|
41
|
+
- InitializationTracker's logic can cause error [\#60](https://github.com/st0012/tapping_device/issues/60)
|
|
42
|
+
|
|
43
|
+
**Closed issues:**
|
|
44
|
+
|
|
45
|
+
- Refactor get\_method\_from\_object [\#59](https://github.com/st0012/tapping_device/issues/59)
|
|
46
|
+
|
|
47
|
+
**Merged pull requests:**
|
|
48
|
+
|
|
49
|
+
- Fix init tracker [\#61](https://github.com/st0012/tapping_device/pull/61) ([st0012](https://github.com/st0012))
|
|
50
|
+
|
|
51
|
+
## [v0.5.4](https://github.com/st0012/tapping_device/tree/v0.5.4) (2020-07-05)
|
|
52
|
+
|
|
53
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.3...v0.5.4)
|
|
54
|
+
|
|
55
|
+
**Closed issues:**
|
|
56
|
+
|
|
57
|
+
- Add with\_print\_calls method [\#52](https://github.com/st0012/tapping_device/issues/52)
|
|
58
|
+
- Tapping any instance of class [\#51](https://github.com/st0012/tapping_device/issues/51)
|
|
59
|
+
- Add ignore\_private option [\#50](https://github.com/st0012/tapping_device/issues/50)
|
|
60
|
+
|
|
61
|
+
**Merged pull requests:**
|
|
62
|
+
|
|
63
|
+
- Restructure README.md [\#58](https://github.com/st0012/tapping_device/pull/58) ([st0012](https://github.com/st0012))
|
|
64
|
+
- Better support on private methods [\#57](https://github.com/st0012/tapping_device/pull/57) ([st0012](https://github.com/st0012))
|
|
65
|
+
- Add with\_\* helpers \(e.g. with\_print\_calls\) [\#56](https://github.com/st0012/tapping_device/pull/56) ([st0012](https://github.com/st0012))
|
|
66
|
+
- Add force\_recording option for debugging [\#55](https://github.com/st0012/tapping_device/pull/55) ([st0012](https://github.com/st0012))
|
|
67
|
+
- Add print\_instance\_\* and write\_instance\_\* helpers [\#54](https://github.com/st0012/tapping_device/pull/54) ([st0012](https://github.com/st0012))
|
|
68
|
+
- Fix tap\_init by adding c\_\* event type [\#53](https://github.com/st0012/tapping_device/pull/53) ([st0012](https://github.com/st0012))
|
|
69
|
+
|
|
70
|
+
## [v0.5.3](https://github.com/st0012/tapping_device/tree/v0.5.3) (2020-06-21)
|
|
71
|
+
|
|
72
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.2...v0.5.3)
|
|
6
73
|
|
|
7
74
|
**Closed issues:**
|
|
8
75
|
|
|
76
|
+
- Global Configuration [\#46](https://github.com/st0012/tapping_device/issues/46)
|
|
9
77
|
- Support write\_\* helpers [\#44](https://github.com/st0012/tapping_device/issues/44)
|
|
10
78
|
- Use Method\#source to replace Payload\#method\_head’s implementation [\#19](https://github.com/st0012/tapping_device/issues/19)
|
|
11
79
|
|
|
12
80
|
**Merged pull requests:**
|
|
13
81
|
|
|
82
|
+
- Support Global Configuration [\#48](https://github.com/st0012/tapping_device/pull/48) ([st0012](https://github.com/st0012))
|
|
14
83
|
- Support write\_\* helpers [\#47](https://github.com/st0012/tapping_device/pull/47) ([st0012](https://github.com/st0012))
|
|
15
84
|
- Hijack attr methods with `hijack\_attr\_methods` option [\#45](https://github.com/st0012/tapping_device/pull/45) ([st0012](https://github.com/st0012))
|
|
16
85
|
|
data/Gemfile
CHANGED
|
@@ -2,3 +2,14 @@ source "https://rubygems.org"
|
|
|
2
2
|
|
|
3
3
|
# Specify your gem's dependencies in tapping_device.gemspec
|
|
4
4
|
gemspec
|
|
5
|
+
|
|
6
|
+
rails_version = ENV["RAILS_VERSION"]
|
|
7
|
+
rails_version = "6.1.0" if rails_version.nil?
|
|
8
|
+
|
|
9
|
+
if rails_version.to_f < 6
|
|
10
|
+
gem "sqlite3", "~> 1.3.0"
|
|
11
|
+
else
|
|
12
|
+
gem "sqlite3"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
gem "activerecord", "~> #{rails_version}"
|
data/Makefile
ADDED
data/README.md
CHANGED
|
@@ -8,187 +8,119 @@
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
## Introduction
|
|
11
|
-
|
|
11
|
+
As the name states, `TappingDevice` allows you to secretly listen to different events of an object:
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
- `Method Calls` - what does the object do
|
|
14
|
+
- `Traces` - how is the object used by the application
|
|
15
|
+
- `State Mutations` - what happens inside the object
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
After collecting the events, `TappingDevice` will output them in a nice, readable format to either stdout or a file.
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
- `print_traces(object)` to see how the object interacts with other objects (like used as an argument)
|
|
19
|
-
- `print_mutations(object)` to see what actions changed the object's state (instance variables)
|
|
19
|
+
**Ultimately, its goal is to let you know all the information you need for debugging with just 1 line of code.**
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
## Usages
|
|
22
22
|
|
|
23
|
-
###
|
|
23
|
+
### Track Method Calls
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
```ruby
|
|
28
|
-
def create
|
|
29
|
-
@manager_params = create_params
|
|
30
|
-
@manager_params[:first_post_checks] = !is_api?
|
|
31
|
-
|
|
32
|
-
manager = NewPostManager.new(current_user, @manager_params)
|
|
33
|
-
|
|
34
|
-
if is_api?
|
|
35
|
-
memoized_payload = DistributedMemoizer.memoize(signature_for(@manager_params), 120) do
|
|
36
|
-
result = manager.perform
|
|
37
|
-
MultiJson.dump(serialize_data(result, NewPostResultSerializer, root: false))
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
parsed_payload = JSON.parse(memoized_payload)
|
|
41
|
-
backwards_compatible_json(parsed_payload, parsed_payload['success'])
|
|
42
|
-
else
|
|
43
|
-
result = manager.perform
|
|
44
|
-
json = serialize_data(result, NewPostResultSerializer, root: false)
|
|
45
|
-
backwards_compatible_json(json, result.success?)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
As you can see, it doesn't even exist in the controller action, which makes tracking it by reading code very hard to do.
|
|
51
|
-
|
|
52
|
-
But with `TappingDevice`. You can use `print_calls` to show what method calls the object performs
|
|
53
|
-
|
|
54
|
-
```ruby
|
|
55
|
-
def create
|
|
56
|
-
# you can retrieve the current guardian object by calling guardian in the controller
|
|
57
|
-
print_calls(guardian)
|
|
58
|
-
@manager_params = create_params
|
|
59
|
-
|
|
60
|
-
# .....
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Now, if you execute the code, like via tests:
|
|
64
|
-
|
|
65
|
-
```shell
|
|
66
|
-
$ rspec spec/requests/posts_controller_spec.rb:603
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
You can get all the method calls it performs with basically everything you need to know
|
|
25
|
+
By tracking an object's method calls, you'll be able to observe the object's behavior very easily
|
|
70
26
|
|
|
71
27
|
<img src="https://github.com/st0012/tapping_device/blob/master/images/print_calls.png" alt="image of print_calls output" width="50%">
|
|
72
28
|
|
|
73
|
-
|
|
74
|
-
- method name
|
|
75
|
-
-
|
|
29
|
+
Each entry consists of 5 pieces of information:
|
|
30
|
+
- method name
|
|
31
|
+
- source of the method
|
|
76
32
|
- call site
|
|
77
33
|
- arguments
|
|
78
34
|
- return value
|
|
79
35
|
|
|
80
36
|

|
|
81
37
|
|
|
82
|
-
|
|
38
|
+
#### Helpers
|
|
83
39
|
|
|
40
|
+
- `print_calls(object)` - prints the result to stdout
|
|
41
|
+
- `write_calls(object, log_file: "file_name")` - writes the result to a file
|
|
42
|
+
- the default file is `/tmp/tapping_device.log`, but you can change it with `log_file: "new_path"` option
|
|
84
43
|
|
|
85
|
-
|
|
44
|
+
#### Use Cases
|
|
45
|
+
- Understand a service object/form object's behavior
|
|
46
|
+
- Debug a messy controller
|
|
86
47
|
|
|
87
|
-
|
|
48
|
+
### Track Traces
|
|
88
49
|
|
|
89
|
-
|
|
90
|
-
def create
|
|
91
|
-
@manager_params = create_params
|
|
92
|
-
@manager_params[:first_post_checks] = !is_api?
|
|
93
|
-
|
|
94
|
-
manager = NewPostManager.new(current_user, @manager_params)
|
|
95
|
-
|
|
96
|
-
print_traces(manager)
|
|
97
|
-
# .....
|
|
98
|
-
```
|
|
50
|
+
By tracking an object's traces, you'll be able to observe the object's journey in your application
|
|
99
51
|
|
|
100
|
-
|
|
52
|
+

|
|
101
53
|
|
|
102
|
-
|
|
103
|
-
$ rspec spec/requests/posts_controller_spec.rb:603
|
|
104
|
-
```
|
|
54
|
+
#### Helpers
|
|
105
55
|
|
|
106
|
-
|
|
56
|
+
- `print_traces(object)` - prints the result to stdout
|
|
57
|
+
- `write_traces(object, log_file: "file_name")` - writes the result to a file
|
|
58
|
+
- the default file is `/tmp/tapping_device.log`, but you can change it with `log_file: "new_path"` option
|
|
107
59
|
|
|
108
|
-
|
|
60
|
+
#### Use Cases
|
|
61
|
+
- Debug argument related issues
|
|
62
|
+
- Understand how a library uses your objects
|
|
109
63
|
|
|
110
|
-
###
|
|
64
|
+
### Track State Mutations
|
|
111
65
|
|
|
112
|
-
|
|
66
|
+
By tracking an object's traces, you'll be able to observe the state changes happen inside the object between each method call
|
|
113
67
|
|
|
114
|
-
|
|
68
|
+
<img src="https://github.com/st0012/tapping_device/blob/master/images/print_mutations.png" alt="image of print_mutations output" width="50%">
|
|
115
69
|
|
|
116
|
-
|
|
117
|
-
# app/controllers/posts_controller.rb
|
|
118
|
-
class PostsController
|
|
119
|
-
def update
|
|
120
|
-
# ......
|
|
121
|
-
revisor = PostRevisor.new(post, topic)
|
|
122
|
-
revisor.revise!(current_user, changes, opts)
|
|
123
|
-
# ......
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
```
|
|
70
|
+
#### Helpers
|
|
127
71
|
|
|
128
|
-
|
|
72
|
+
- `print_mutations(object)` - prints the result to stdout
|
|
73
|
+
- `write_mutations(object, log_file: "file_name")` - writes the result to a file
|
|
74
|
+
- the default file is `/tmp/tapping_device.log`, but you can change it with `log_file: "new_path"` option
|
|
129
75
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
@editor = editor
|
|
134
|
-
@fields = fields.with_indifferent_access
|
|
135
|
-
@opts = opts
|
|
76
|
+
#### Use Cases
|
|
77
|
+
- Debug state related issues
|
|
78
|
+
- Debug memoization issues
|
|
136
79
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# ......
|
|
80
|
+
### Track All Instances Of A Class
|
|
140
81
|
|
|
141
|
-
|
|
142
|
-
@last_version_at = @post.last_version_at || Time.now
|
|
82
|
+
It's not always easy to directly access the objects we want to track, especially when they're managed by a library (e.g. `ActiveRecord::Relation`). In such cases, you can use these helpers to track the class's instances:
|
|
143
83
|
|
|
144
|
-
|
|
145
|
-
|
|
84
|
+
- `print_instance_calls(ObjectKlass)`
|
|
85
|
+
- `print_instance_traces(ObjectKlass)`
|
|
86
|
+
- `print_instance_mutations(ObjectKlass)`
|
|
87
|
+
- `write_instance_calls(ObjectKlass)`
|
|
88
|
+
- `write_instance_traces(ObjectKlass)`
|
|
89
|
+
- `write_instance_mutations(ObjectKlass)`
|
|
146
90
|
|
|
147
|
-
@validate_post = true
|
|
148
|
-
# ......
|
|
149
|
-
end
|
|
150
|
-
```
|
|
151
91
|
|
|
152
|
-
|
|
92
|
+
### Use `with_HELPER_NAME` for chained method calls
|
|
153
93
|
|
|
154
|
-
|
|
94
|
+
In Ruby programs, we often chain multiple methods together like this:
|
|
155
95
|
|
|
156
96
|
```ruby
|
|
157
|
-
|
|
158
|
-
class PostsController
|
|
159
|
-
def update
|
|
160
|
-
# ......
|
|
161
|
-
revisor = PostRevisor.new(post, topic)
|
|
162
|
-
print_mutations(revisor)
|
|
163
|
-
revisor.revise!(current_user, changes, opts)
|
|
164
|
-
# ......
|
|
165
|
-
end
|
|
166
|
-
end
|
|
97
|
+
SomeService.new(params).perform
|
|
167
98
|
```
|
|
168
99
|
|
|
169
|
-
And
|
|
100
|
+
And to debug it, we'll need to break the method chain into
|
|
170
101
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
102
|
+
```ruby
|
|
103
|
+
service = SomeService.new(params)
|
|
104
|
+
print_calls(service, options)
|
|
105
|
+
service.perform
|
|
106
|
+
```
|
|
176
107
|
|
|
177
|
-
|
|
108
|
+
This kind of code changes are usually annoying, and that's one of the problems I want to solve with `TappingDevice`.
|
|
178
109
|
|
|
179
|
-
|
|
110
|
+
So here's another option, just insert a `with_HELPER_NAME` call in between:
|
|
180
111
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
112
|
+
```ruby
|
|
113
|
+
SomeService.new(params).with_print_calls(options).perform
|
|
114
|
+
```
|
|
184
115
|
|
|
185
|
-
|
|
116
|
+
And it'll behave exactly like
|
|
186
117
|
|
|
187
118
|
```ruby
|
|
188
|
-
|
|
119
|
+
service = SomeService.new(params)
|
|
120
|
+
print_calls(service, options)
|
|
121
|
+
service.perform
|
|
189
122
|
```
|
|
190
123
|
|
|
191
|
-
|
|
192
124
|
## Installation
|
|
193
125
|
Add this line to your application's Gemfile:
|
|
194
126
|
|
|
@@ -296,6 +228,40 @@ The default is `false` because
|
|
|
296
228
|
2. It's still unclear if this hack safe enough for most applications.
|
|
297
229
|
|
|
298
230
|
|
|
231
|
+
#### `ignore_private`
|
|
232
|
+
|
|
233
|
+
Sometimes we use many private methods to perform trivial operations, like
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
class Operation
|
|
237
|
+
def extras
|
|
238
|
+
dig_attribute("extras")
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
private
|
|
242
|
+
|
|
243
|
+
def data
|
|
244
|
+
@data
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def dig_attribute(attr)
|
|
248
|
+
data.dig("attributes", attr)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
And we may not be interested in those method calls. If that's the case, you can use the `ignore_private` option
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
operation = Operation.new(params)
|
|
257
|
+
print_calls(operation, ignore_private: true) #=> only prints the `extras` call
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### `only_private`
|
|
261
|
+
|
|
262
|
+
This option does the opposite of the `ignore_private` option does.
|
|
263
|
+
|
|
264
|
+
|
|
299
265
|
### Global Configuration
|
|
300
266
|
|
|
301
267
|
If you don't want to pass options every time you use a helper, you can use global configuration to change the default values:
|
|
@@ -341,3 +307,4 @@ The gem is available as open-source under the terms of the [MIT License](https:/
|
|
|
341
307
|
## Code of Conduct
|
|
342
308
|
|
|
343
309
|
Everyone interacting in the TappingDevice project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/tapping_device/blob/master/CODE_OF_CONDUCT.md).
|
|
310
|
+
|
data/lib/tapping_device.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
require "active_record"
|
|
2
1
|
require "active_support/core_ext/module/introspection"
|
|
3
2
|
require "pry" # for using Method#source
|
|
4
3
|
|
|
@@ -34,7 +33,7 @@ class TappingDevice
|
|
|
34
33
|
def initialize(options = {}, &block)
|
|
35
34
|
@block = block
|
|
36
35
|
@output_block = nil
|
|
37
|
-
@options = process_options(options)
|
|
36
|
+
@options = process_options(options.dup)
|
|
38
37
|
@calls = []
|
|
39
38
|
@disabled = false
|
|
40
39
|
@with_condition = nil
|
|
@@ -94,15 +93,19 @@ class TappingDevice
|
|
|
94
93
|
private
|
|
95
94
|
|
|
96
95
|
def build_minimum_trace_point(event_type:)
|
|
97
|
-
TracePoint.new(event_type) do |tp|
|
|
96
|
+
TracePoint.new(*event_type) do |tp|
|
|
98
97
|
next unless filter_condition_satisfied?(tp)
|
|
99
|
-
next if is_tapping_device_call?(tp)
|
|
100
98
|
|
|
101
99
|
filepath, line_number = get_call_location(tp)
|
|
102
100
|
payload = build_payload(tp: tp, filepath: filepath, line_number: line_number)
|
|
103
101
|
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
unless @options[:force_recording]
|
|
103
|
+
next if is_tapping_device_call?(tp)
|
|
104
|
+
next if should_be_skipped_by_paths?(filepath)
|
|
105
|
+
next unless with_condition_satisfied?(payload)
|
|
106
|
+
next if payload.is_private_call? && @options[:ignore_private]
|
|
107
|
+
next if !payload.is_private_call? && @options[:only_private]
|
|
108
|
+
end
|
|
106
109
|
|
|
107
110
|
yield(payload)
|
|
108
111
|
end
|
|
@@ -137,7 +140,7 @@ class TappingDevice
|
|
|
137
140
|
end
|
|
138
141
|
|
|
139
142
|
def build_payload(tp:, filepath:, line_number:)
|
|
140
|
-
Payload.
|
|
143
|
+
Payload.new(
|
|
141
144
|
target: @target,
|
|
142
145
|
receiver: tp.self,
|
|
143
146
|
method_name: tp.callee_id,
|
|
@@ -148,15 +151,14 @@ class TappingDevice
|
|
|
148
151
|
line_number: line_number,
|
|
149
152
|
defined_class: tp.defined_class,
|
|
150
153
|
trace: get_traces(tp),
|
|
154
|
+
is_private_call: tp.defined_class.private_method_defined?(tp.callee_id),
|
|
155
|
+
tag: options[:tag],
|
|
151
156
|
tp: tp
|
|
152
|
-
|
|
157
|
+
)
|
|
153
158
|
end
|
|
154
159
|
|
|
155
160
|
def get_method_object_from(target, method_name)
|
|
156
|
-
|
|
157
|
-
rescue ArgumentError
|
|
158
|
-
method_method = Object.method(:method).unbind
|
|
159
|
-
method_method.bind(target).call(method_name)
|
|
161
|
+
Object.instance_method(:method).bind(target).call(method_name)
|
|
160
162
|
rescue NameError
|
|
161
163
|
# if any part of the program uses Refinement to extend its methods
|
|
162
164
|
# we might still get NoMethodError when trying to get that method outside the scope
|
|
@@ -204,6 +206,10 @@ class TappingDevice
|
|
|
204
206
|
options[:event_type] ||= config[:event_type]
|
|
205
207
|
options[:hijack_attr_methods] ||= config[:hijack_attr_methods]
|
|
206
208
|
options[:track_as_records] ||= config[:track_as_records]
|
|
209
|
+
options[:ignore_private] ||= config[:ignore_private]
|
|
210
|
+
options[:only_private] ||= config[:only_private]
|
|
211
|
+
# for debugging the gem more easily
|
|
212
|
+
options[:force_recording] ||= false
|
|
207
213
|
|
|
208
214
|
options[:descendants] ||= []
|
|
209
215
|
options[:root_device] ||= self
|
|
@@ -20,10 +20,14 @@ class TappingDevice
|
|
|
20
20
|
|
|
21
21
|
def is_writer_method?(method_name)
|
|
22
22
|
has_definition_source?(method_name) && method_name.match?(/\w+=/) && target.method(method_name).source.match?(/attr_writer|attr_accessor/)
|
|
23
|
+
rescue MethodSource::SourceNotFoundError
|
|
24
|
+
false
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def is_reader_method?(method_name)
|
|
26
28
|
has_definition_source?(method_name) && target.method(method_name).source.match?(/attr_reader|attr_accessor/)
|
|
29
|
+
rescue MethodSource::SourceNotFoundError
|
|
30
|
+
false
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
def has_definition_source?(method_name)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "logger"
|
|
2
|
+
require "tapping_device/output/payload_wrapper"
|
|
2
3
|
require "tapping_device/output/writer"
|
|
3
|
-
require "tapping_device/output/stdout_writer"
|
|
4
|
-
require "tapping_device/output/file_writer"
|
|
5
4
|
|
|
6
5
|
class TappingDevice
|
|
7
6
|
module Output
|
|
@@ -13,16 +12,16 @@ class TappingDevice
|
|
|
13
12
|
|
|
14
13
|
module Helpers
|
|
15
14
|
def and_write(payload_method = nil, options: {}, &block)
|
|
16
|
-
and_output(payload_method, options: options,
|
|
15
|
+
and_output(payload_method, options: options, logger: Logger.new(options[:log_file]), &block)
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def and_print(payload_method = nil, options: {}, &block)
|
|
20
|
-
and_output(payload_method, options: options,
|
|
19
|
+
and_output(payload_method, options: options, logger: Logger.new($stdout), &block)
|
|
21
20
|
end
|
|
22
21
|
|
|
23
|
-
def and_output(payload_method = nil, options: {},
|
|
22
|
+
def and_output(payload_method = nil, options: {}, logger:, &block)
|
|
24
23
|
output_block = generate_output_block(payload_method, block)
|
|
25
|
-
@output_writer =
|
|
24
|
+
@output_writer = Writer.new(options: options, output_block: output_block, logger: logger)
|
|
26
25
|
self
|
|
27
26
|
end
|
|
28
27
|
|
|
@@ -1,13 +1,44 @@
|
|
|
1
|
+
require "pastel"
|
|
2
|
+
|
|
1
3
|
class TappingDevice
|
|
2
4
|
module Output
|
|
3
|
-
class
|
|
5
|
+
class PayloadWrapper
|
|
4
6
|
UNDEFINED = "[undefined]"
|
|
7
|
+
PRIVATE_MARK = " (private)"
|
|
8
|
+
|
|
9
|
+
PASTEL = Pastel.new
|
|
10
|
+
PASTEL.alias_color(:orange, :bright_red, :bright_yellow)
|
|
11
|
+
|
|
12
|
+
TappingDevice::Payload::ATTRS.each do |attr|
|
|
13
|
+
define_method attr do |options = {}|
|
|
14
|
+
@payload.send(attr)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
alias :is_private_call? :is_private_call
|
|
19
|
+
|
|
20
|
+
def method_head
|
|
21
|
+
@payload.method_head
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def location(options = {})
|
|
25
|
+
@payload.location(options)
|
|
26
|
+
end
|
|
5
27
|
|
|
6
28
|
alias :raw_arguments :arguments
|
|
7
29
|
alias :raw_return_value :return_value
|
|
8
30
|
|
|
31
|
+
def initialize(payload)
|
|
32
|
+
@payload = payload
|
|
33
|
+
end
|
|
34
|
+
|
|
9
35
|
def method_name(options = {})
|
|
10
|
-
":#{
|
|
36
|
+
name = ":#{@payload.method_name}"
|
|
37
|
+
|
|
38
|
+
name += " [#{tag}]" if tag
|
|
39
|
+
name += PRIVATE_MARK if is_private_call?
|
|
40
|
+
|
|
41
|
+
name
|
|
11
42
|
end
|
|
12
43
|
|
|
13
44
|
def arguments(options = {})
|
|
@@ -18,29 +49,13 @@ class TappingDevice
|
|
|
18
49
|
generate_string_result(raw_return_value, options[:inspect])
|
|
19
50
|
end
|
|
20
51
|
|
|
21
|
-
COLOR_CODES = {
|
|
22
|
-
green: 10,
|
|
23
|
-
yellow: 11,
|
|
24
|
-
blue: 12,
|
|
25
|
-
megenta: 13,
|
|
26
|
-
cyan: 14,
|
|
27
|
-
orange: 214
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
COLORS = COLOR_CODES.each_with_object({}) do |(name, code), hash|
|
|
31
|
-
hash[name] = "\u001b[38;5;#{code}m"
|
|
32
|
-
end.merge(
|
|
33
|
-
reset: "\u001b[0m",
|
|
34
|
-
nocolor: ""
|
|
35
|
-
)
|
|
36
|
-
|
|
37
52
|
PAYLOAD_ATTRIBUTES = {
|
|
38
|
-
method_name: {symbol: "", color:
|
|
39
|
-
location: {symbol: "from:", color:
|
|
40
|
-
return_value: {symbol: "=>", color:
|
|
41
|
-
arguments: {symbol: "<=", color:
|
|
42
|
-
ivar_changes: {symbol: "changes:\n", color:
|
|
43
|
-
defined_class: {symbol: "#", color:
|
|
53
|
+
method_name: {symbol: "", color: :bright_blue},
|
|
54
|
+
location: {symbol: "from:", color: :green},
|
|
55
|
+
return_value: {symbol: "=>", color: :magenta},
|
|
56
|
+
arguments: {symbol: "<=", color: :orange},
|
|
57
|
+
ivar_changes: {symbol: "changes:\n", color: :blue},
|
|
58
|
+
defined_class: {symbol: "#", color: :yellow}
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
PAYLOAD_ATTRIBUTES.each do |attribute, attribute_options|
|
|
@@ -53,7 +68,7 @@ class TappingDevice
|
|
|
53
68
|
call_result = send("original_#{attribute}", options)
|
|
54
69
|
|
|
55
70
|
if options[:colorize]
|
|
56
|
-
|
|
71
|
+
PASTEL.send(color, call_result)
|
|
57
72
|
else
|
|
58
73
|
call_result
|
|
59
74
|
end
|
|
@@ -83,7 +98,7 @@ class TappingDevice
|
|
|
83
98
|
return unless arg_name
|
|
84
99
|
|
|
85
100
|
arg_name = ":#{arg_name}"
|
|
86
|
-
arg_name =
|
|
101
|
+
arg_name = PASTEL.orange(arg_name) if options[:colorize]
|
|
87
102
|
msg = "Passed as #{arg_name} in '#{defined_class(options)}##{method_name(options)}' at #{location(options)}\n"
|
|
88
103
|
msg += " > #{method_head}\n" if with_method_head
|
|
89
104
|
msg
|
|
@@ -100,17 +115,17 @@ class TappingDevice
|
|
|
100
115
|
end
|
|
101
116
|
|
|
102
117
|
def ivar_changes(options = {})
|
|
103
|
-
|
|
118
|
+
@payload.ivar_changes.map do |ivar, value_changes|
|
|
104
119
|
before = generate_string_result(value_changes[:before], options[:inspect])
|
|
105
120
|
after = generate_string_result(value_changes[:after], options[:inspect])
|
|
106
121
|
|
|
107
122
|
if options[:colorize]
|
|
108
|
-
ivar =
|
|
109
|
-
before =
|
|
110
|
-
after =
|
|
123
|
+
ivar = PASTEL.orange(ivar)
|
|
124
|
+
before = PASTEL.bright_blue(before.to_s)
|
|
125
|
+
after = PASTEL.bright_blue(after.to_s)
|
|
111
126
|
end
|
|
112
127
|
|
|
113
|
-
" #{ivar}: #{before
|
|
128
|
+
" #{ivar}: #{before} => #{after}"
|
|
114
129
|
end.join("\n")
|
|
115
130
|
end
|
|
116
131
|
|
|
@@ -126,10 +141,6 @@ class TappingDevice
|
|
|
126
141
|
|
|
127
142
|
private
|
|
128
143
|
|
|
129
|
-
def value_with_color(value, color)
|
|
130
|
-
"#{COLORS[color]}#{value}#{COLORS[:reset]}"
|
|
131
|
-
end
|
|
132
|
-
|
|
133
144
|
def generate_string_result(obj, inspect)
|
|
134
145
|
case obj
|
|
135
146
|
when Array
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
class TappingDevice
|
|
2
2
|
module Output
|
|
3
3
|
class Writer
|
|
4
|
-
def initialize(options
|
|
4
|
+
def initialize(options:, output_block:, logger:)
|
|
5
5
|
@options = options
|
|
6
6
|
@output_block = output_block
|
|
7
|
+
@logger = logger
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
def write!(payload)
|
|
10
|
-
|
|
11
|
+
output = generate_output(payload)
|
|
12
|
+
@logger << output
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
private
|
|
14
16
|
|
|
15
17
|
def generate_output(payload)
|
|
16
|
-
@output_block.call(
|
|
18
|
+
@output_block.call(PayloadWrapper.new(payload), @options)
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
end
|
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
class TappingDevice
|
|
2
|
-
class Payload
|
|
2
|
+
class Payload
|
|
3
3
|
ATTRS = [
|
|
4
4
|
:target, :receiver, :method_name, :method_object, :arguments, :return_value, :filepath, :line_number,
|
|
5
|
-
:defined_class, :trace, :tp, :ivar_changes
|
|
5
|
+
:defined_class, :trace, :tag, :tp, :ivar_changes, :is_private_call
|
|
6
6
|
]
|
|
7
7
|
|
|
8
|
-
ATTRS
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
end
|
|
12
|
-
end
|
|
8
|
+
attr_accessor(*ATTRS)
|
|
9
|
+
|
|
10
|
+
alias :is_private_call? :is_private_call
|
|
13
11
|
|
|
14
|
-
def
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
def initialize(
|
|
13
|
+
target:, receiver:, method_name:, method_object:, arguments:, return_value:, filepath:, line_number:,
|
|
14
|
+
defined_class:, trace:, tag:, tp:, is_private_call:
|
|
15
|
+
)
|
|
16
|
+
@target = target
|
|
17
|
+
@receiver = receiver
|
|
18
|
+
@method_name = method_name
|
|
19
|
+
@method_object = method_object
|
|
20
|
+
@arguments = arguments
|
|
21
|
+
@return_value = return_value
|
|
22
|
+
@filepath = filepath
|
|
23
|
+
@line_number = line_number
|
|
24
|
+
@defined_class = defined_class
|
|
25
|
+
@trace = trace
|
|
26
|
+
@tag = tag
|
|
27
|
+
@tp = tp
|
|
28
|
+
@ivar_changes = {}
|
|
29
|
+
@is_private_call = is_private_call
|
|
20
30
|
end
|
|
21
31
|
|
|
22
32
|
def method_head
|
|
@@ -20,28 +20,29 @@ class TappingDevice
|
|
|
20
20
|
TappingDevice::Trackers::MutationTracker.new(options, &block).track(object)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
[:calls, :traces, :mutations].each do |subject|
|
|
24
|
+
[:print, :write].each do |output_action|
|
|
25
|
+
helper_method_name = "#{output_action}_#{subject}"
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
define_method helper_method_name do |target, options = {}|
|
|
28
|
+
send("output_#{subject}", target, options, output_action: "and_#{output_action}")
|
|
29
|
+
end
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
define_method "with_#{helper_method_name}" do |options = {}|
|
|
32
|
+
send(helper_method_name, self, options)
|
|
33
|
+
self
|
|
34
|
+
end
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
end
|
|
36
|
+
define_method "#{output_action}_instance_#{subject}" do |target_klass, options = {}|
|
|
37
|
+
collection_proxy = AsyncCollectionProxy.new
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
tap_init!(target_klass, options.merge(force_recording: true)) do |payload|
|
|
40
|
+
collection_proxy << send(helper_method_name, payload.return_value, options)
|
|
41
|
+
end
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
collection_proxy
|
|
44
|
+
end
|
|
45
|
+
end
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
private
|
|
@@ -84,12 +85,15 @@ class TappingDevice
|
|
|
84
85
|
[options, output_options]
|
|
85
86
|
end
|
|
86
87
|
|
|
88
|
+
# CollectionProxy delegates chained actions to multiple devices
|
|
87
89
|
class CollectionProxy
|
|
90
|
+
CHAINABLE_ACTIONS = [:stop!, :stop_when, :with]
|
|
91
|
+
|
|
88
92
|
def initialize(devices)
|
|
89
93
|
@devices = devices
|
|
90
94
|
end
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
CHAINABLE_ACTIONS.each do |method|
|
|
93
97
|
define_method method do |&block|
|
|
94
98
|
@devices.each do |device|
|
|
95
99
|
device.send(method, &block)
|
|
@@ -97,6 +101,32 @@ class TappingDevice
|
|
|
97
101
|
end
|
|
98
102
|
end
|
|
99
103
|
end
|
|
104
|
+
|
|
105
|
+
# AsyncCollectionProxy delegates chained actions to multiple device "asyncronously"
|
|
106
|
+
# when we use tapping methods like `tap_init!` to create sub-devices
|
|
107
|
+
# we need to find a way to pass the chained actions to every sub-device that's created
|
|
108
|
+
# and this can only happen asyncronously as we won't know when'll that happen
|
|
109
|
+
class AsyncCollectionProxy < CollectionProxy
|
|
110
|
+
def initialize(devices = [])
|
|
111
|
+
super
|
|
112
|
+
@blocks = {}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
CHAINABLE_ACTIONS.each do |method|
|
|
116
|
+
define_method method do |&block|
|
|
117
|
+
super(&block)
|
|
118
|
+
@blocks[method] = block
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def <<(device)
|
|
123
|
+
@devices << device
|
|
124
|
+
|
|
125
|
+
@blocks.each do |method, block|
|
|
126
|
+
device.send(method, &block)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
100
130
|
end
|
|
101
131
|
end
|
|
102
132
|
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
class TappingDevice
|
|
2
2
|
module Trackers
|
|
3
3
|
class InitializationTracker < TappingDevice
|
|
4
|
+
def initialize(options = {}, &block)
|
|
5
|
+
super
|
|
6
|
+
event_type = @options[:event_type]
|
|
7
|
+
# if a class doesn't override the 'initialize' method
|
|
8
|
+
# Class.new will only trigger c_return or c_call
|
|
9
|
+
@options[:event_type] = [event_type, "c_#{event_type}"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def track(object)
|
|
13
|
+
super
|
|
14
|
+
@is_active_record_model = defined?(ActiveRecord) && target.ancestors.include?(ActiveRecord::Base)
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
|
|
4
18
|
def build_payload(tp:, filepath:, line_number:)
|
|
5
19
|
payload = super
|
|
6
|
-
|
|
7
|
-
payload
|
|
20
|
+
|
|
21
|
+
return payload if @is_active_record_model
|
|
22
|
+
|
|
23
|
+
payload.return_value = payload.receiver
|
|
24
|
+
payload.receiver = target
|
|
8
25
|
payload
|
|
9
26
|
end
|
|
10
27
|
|
|
@@ -16,8 +33,17 @@ class TappingDevice
|
|
|
16
33
|
receiver = tp.self
|
|
17
34
|
method_name = tp.callee_id
|
|
18
35
|
|
|
19
|
-
if
|
|
20
|
-
|
|
36
|
+
if @is_active_record_model
|
|
37
|
+
# ActiveRecord redefines model classes' .new method,
|
|
38
|
+
# so instead of calling Model#initialize, it'll actually call Model.new
|
|
39
|
+
# see https://github.com/rails/rails/blob/master/activerecord/lib/active_record/inheritance.rb#L50
|
|
40
|
+
method_name == :new &&
|
|
41
|
+
receiver.is_a?(Class) &&
|
|
42
|
+
# this checks if the model class is the target class or a subclass of it
|
|
43
|
+
receiver.ancestors.include?(target) &&
|
|
44
|
+
# Model.new triggers both c_return and return events. so we should only return in 1 type of the events
|
|
45
|
+
# otherwise the callback will be triggered twice
|
|
46
|
+
tp.event == :return
|
|
21
47
|
else
|
|
22
48
|
method_name == :initialize && receiver.is_a?(target)
|
|
23
49
|
end
|
|
@@ -47,7 +47,7 @@ class TappingDevice
|
|
|
47
47
|
payload = super
|
|
48
48
|
|
|
49
49
|
if change_capturing_event?(tp)
|
|
50
|
-
payload
|
|
50
|
+
payload.ivar_changes = capture_ivar_changes
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
payload
|
|
@@ -55,15 +55,14 @@ class TappingDevice
|
|
|
55
55
|
|
|
56
56
|
def capture_ivar_changes
|
|
57
57
|
changes = {}
|
|
58
|
-
|
|
59
58
|
additional_keys = @latest_instance_variables.keys - @instance_variables_snapshot.keys
|
|
60
59
|
additional_keys.each do |key|
|
|
61
|
-
changes[key] = {before: Output::
|
|
60
|
+
changes[key] = {before: Output::PayloadWrapper::UNDEFINED, after: @latest_instance_variables[key]}
|
|
62
61
|
end
|
|
63
62
|
|
|
64
63
|
removed_keys = @instance_variables_snapshot.keys - @latest_instance_variables.keys
|
|
65
64
|
removed_keys.each do |key|
|
|
66
|
-
changes[key] = {before: @instance_variables_snapshot[key], after: Output::
|
|
65
|
+
changes[key] = {before: @instance_variables_snapshot[key], after: Output::PayloadWrapper::UNDEFINED}
|
|
67
66
|
end
|
|
68
67
|
|
|
69
68
|
remained_keys = @latest_instance_variables.keys - additional_keys
|
data/tapping_device.gemspec
CHANGED
|
@@ -26,16 +26,10 @@ Gem::Specification.new do |spec|
|
|
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
27
27
|
spec.require_paths = ["lib"]
|
|
28
28
|
|
|
29
|
-
if ENV["RAILS_VERSION"]
|
|
30
|
-
spec.add_dependency "activerecord", "~> #{ENV["RAILS_VERSION"]}"
|
|
31
|
-
else
|
|
32
|
-
spec.add_dependency "activerecord", ">= 5.2"
|
|
33
|
-
end
|
|
34
|
-
|
|
35
29
|
spec.add_dependency "pry" # for using Method#source in MutationTracker
|
|
36
30
|
spec.add_dependency "activesupport"
|
|
31
|
+
spec.add_dependency "pastel"
|
|
37
32
|
|
|
38
|
-
spec.add_development_dependency "sqlite3", ">= 1.3.6"
|
|
39
33
|
spec.add_development_dependency "database_cleaner"
|
|
40
34
|
spec.add_development_dependency "bundler", "~> 2.0"
|
|
41
35
|
spec.add_development_dependency "rake", "~> 13.0"
|
metadata
CHANGED
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tapping_device
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- st0012
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-04-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: activerecord
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '5.2'
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '5.2'
|
|
27
13
|
- !ruby/object:Gem::Dependency
|
|
28
14
|
name: pry
|
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -53,19 +39,19 @@ dependencies:
|
|
|
53
39
|
- !ruby/object:Gem::Version
|
|
54
40
|
version: '0'
|
|
55
41
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
42
|
+
name: pastel
|
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
|
58
44
|
requirements:
|
|
59
45
|
- - ">="
|
|
60
46
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
62
|
-
type: :
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
63
49
|
prerelease: false
|
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
51
|
requirements:
|
|
66
52
|
- - ">="
|
|
67
53
|
- !ruby/object:Gem::Version
|
|
68
|
-
version:
|
|
54
|
+
version: '0'
|
|
69
55
|
- !ruby/object:Gem::Dependency
|
|
70
56
|
name: database_cleaner
|
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -154,8 +140,8 @@ files:
|
|
|
154
140
|
- CHANGELOG.md
|
|
155
141
|
- CODE_OF_CONDUCT.md
|
|
156
142
|
- Gemfile
|
|
157
|
-
- Gemfile.lock
|
|
158
143
|
- LICENSE.txt
|
|
144
|
+
- Makefile
|
|
159
145
|
- README.md
|
|
160
146
|
- Rakefile
|
|
161
147
|
- bin/console
|
|
@@ -170,9 +156,7 @@ files:
|
|
|
170
156
|
- lib/tapping_device/manageable.rb
|
|
171
157
|
- lib/tapping_device/method_hijacker.rb
|
|
172
158
|
- lib/tapping_device/output.rb
|
|
173
|
-
- lib/tapping_device/output/
|
|
174
|
-
- lib/tapping_device/output/payload.rb
|
|
175
|
-
- lib/tapping_device/output/stdout_writer.rb
|
|
159
|
+
- lib/tapping_device/output/payload_wrapper.rb
|
|
176
160
|
- lib/tapping_device/output/writer.rb
|
|
177
161
|
- lib/tapping_device/payload.rb
|
|
178
162
|
- lib/tapping_device/trackable.rb
|
|
@@ -205,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
205
189
|
- !ruby/object:Gem::Version
|
|
206
190
|
version: '0'
|
|
207
191
|
requirements: []
|
|
208
|
-
rubygems_version: 3.
|
|
192
|
+
rubygems_version: 3.2.15
|
|
209
193
|
signing_key:
|
|
210
194
|
specification_version: 4
|
|
211
195
|
summary: tapping_device lets you understand what your Ruby objects do without digging
|
data/Gemfile.lock
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
PATH
|
|
2
|
-
remote: .
|
|
3
|
-
specs:
|
|
4
|
-
tapping_device (0.5.3)
|
|
5
|
-
activerecord (>= 5.2)
|
|
6
|
-
activesupport
|
|
7
|
-
pry
|
|
8
|
-
|
|
9
|
-
GEM
|
|
10
|
-
remote: https://rubygems.org/
|
|
11
|
-
specs:
|
|
12
|
-
activemodel (6.0.3.2)
|
|
13
|
-
activesupport (= 6.0.3.2)
|
|
14
|
-
activerecord (6.0.3.2)
|
|
15
|
-
activemodel (= 6.0.3.2)
|
|
16
|
-
activesupport (= 6.0.3.2)
|
|
17
|
-
activesupport (6.0.3.2)
|
|
18
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
19
|
-
i18n (>= 0.7, < 2)
|
|
20
|
-
minitest (~> 5.1)
|
|
21
|
-
tzinfo (~> 1.1)
|
|
22
|
-
zeitwerk (~> 2.2, >= 2.2.2)
|
|
23
|
-
coderay (1.1.3)
|
|
24
|
-
concurrent-ruby (1.1.6)
|
|
25
|
-
database_cleaner (1.7.0)
|
|
26
|
-
diff-lcs (1.3)
|
|
27
|
-
docile (1.3.2)
|
|
28
|
-
i18n (1.8.3)
|
|
29
|
-
concurrent-ruby (~> 1.0)
|
|
30
|
-
json (2.3.0)
|
|
31
|
-
method_source (1.0.0)
|
|
32
|
-
minitest (5.14.1)
|
|
33
|
-
pry (0.13.1)
|
|
34
|
-
coderay (~> 1.1)
|
|
35
|
-
method_source (~> 1.0)
|
|
36
|
-
rake (13.0.1)
|
|
37
|
-
rspec (3.8.0)
|
|
38
|
-
rspec-core (~> 3.8.0)
|
|
39
|
-
rspec-expectations (~> 3.8.0)
|
|
40
|
-
rspec-mocks (~> 3.8.0)
|
|
41
|
-
rspec-core (3.8.2)
|
|
42
|
-
rspec-support (~> 3.8.0)
|
|
43
|
-
rspec-expectations (3.8.4)
|
|
44
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
45
|
-
rspec-support (~> 3.8.0)
|
|
46
|
-
rspec-mocks (3.8.1)
|
|
47
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
48
|
-
rspec-support (~> 3.8.0)
|
|
49
|
-
rspec-support (3.8.2)
|
|
50
|
-
simplecov (0.17.1)
|
|
51
|
-
docile (~> 1.1)
|
|
52
|
-
json (>= 1.8, < 3)
|
|
53
|
-
simplecov-html (~> 0.10.0)
|
|
54
|
-
simplecov-html (0.10.2)
|
|
55
|
-
sqlite3 (1.4.1)
|
|
56
|
-
thread_safe (0.3.6)
|
|
57
|
-
tzinfo (1.2.7)
|
|
58
|
-
thread_safe (~> 0.1)
|
|
59
|
-
zeitwerk (2.3.0)
|
|
60
|
-
|
|
61
|
-
PLATFORMS
|
|
62
|
-
ruby
|
|
63
|
-
|
|
64
|
-
DEPENDENCIES
|
|
65
|
-
bundler (~> 2.0)
|
|
66
|
-
database_cleaner
|
|
67
|
-
rake (~> 13.0)
|
|
68
|
-
rspec (~> 3.0)
|
|
69
|
-
simplecov (= 0.17.1)
|
|
70
|
-
sqlite3 (>= 1.3.6)
|
|
71
|
-
tapping_device!
|
|
72
|
-
|
|
73
|
-
BUNDLED WITH
|
|
74
|
-
2.1.1
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
class TappingDevice
|
|
2
|
-
module Output
|
|
3
|
-
class FileWriter < Writer
|
|
4
|
-
def initialize(options, output_block)
|
|
5
|
-
@path = options[:log_file]
|
|
6
|
-
|
|
7
|
-
File.write(@path, "") # clean file
|
|
8
|
-
|
|
9
|
-
super
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def write!(payload)
|
|
13
|
-
output = generate_output(payload)
|
|
14
|
-
|
|
15
|
-
File.open(@path, "a") do |f|
|
|
16
|
-
f << output
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|