tapping_device 0.4.9 → 0.5.2
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/.DS_Store +0 -0
- data/.github/workflows/ruby.yml +11 -4
- data/.ruby-version +1 -0
- data/Gemfile.lock +21 -23
- data/README.md +136 -353
- data/images/print_calls - single entry.png +0 -0
- data/images/print_calls.png +0 -0
- data/images/print_mutations.png +0 -0
- data/images/print_traces.png +0 -0
- data/lib/tapping_device.rb +108 -112
- data/lib/tapping_device/exceptions.rb +12 -0
- data/lib/tapping_device/output_payload.rb +173 -0
- data/lib/tapping_device/payload.rb +3 -44
- data/lib/tapping_device/trackable.rb +56 -17
- data/lib/tapping_device/trackers/association_call_tracker.rb +17 -0
- data/lib/tapping_device/trackers/initialization_tracker.rb +27 -0
- data/lib/tapping_device/trackers/method_call_tracker.rb +9 -0
- data/lib/tapping_device/trackers/mutation_tracker.rb +132 -0
- data/lib/tapping_device/trackers/passed_tracker.rb +16 -0
- data/lib/tapping_device/version.rb +1 -1
- data/tapping_device.gemspec +4 -5
- metadata +21 -22
- data/lib/tapping_device/sql_tapping_methods.rb +0 -89
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbf652959b6f125f5e4ece39c59db0b8ebd55096e4aa639452de306857193bf7
|
4
|
+
data.tar.gz: 31d13f1f4f38b6009795e4d948ba4596428355cb03d113603b3801c307c14a18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98eae30679e7e08f7607dbd7ee457b9db9565e46ddf2859a782f2b5ce817bf014937b65f8ee0f342bb7d6dbec3afcb3aef980e396362726839c4031db03cb2e5
|
7
|
+
data.tar.gz: bf2d0e6e8e38466f928d57580edf745b1ee1ce18ed1ee03968dc76647b67cfb4e0feaeff0166ac7503c295c54351445344f828a0bc3878b57a74ed8a130a03cb
|
data/.DS_Store
ADDED
Binary file
|
data/.github/workflows/ruby.yml
CHANGED
@@ -33,10 +33,17 @@ jobs:
|
|
33
33
|
gem install bundler
|
34
34
|
bundle install --jobs 4 --retry 3
|
35
35
|
|
36
|
-
- name: Run test with Rails ${{ matrix.rails_version }}
|
37
|
-
|
36
|
+
- name: Run test with Rails ${{ matrix.rails_version }}
|
37
|
+
run: bundle exec rake
|
38
|
+
|
39
|
+
- name: Publish Test Coverage
|
40
|
+
uses: paambaati/codeclimate-action@v2.6.0
|
38
41
|
env:
|
39
|
-
CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}}
|
42
|
+
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
43
|
+
if: ${{ env.CC_TEST_REPORTER_ID != '' }}
|
40
44
|
with:
|
41
|
-
|
45
|
+
# the coverage result should already be generated by the previous step
|
46
|
+
# so we don't need to provide and command in the step
|
47
|
+
# this is just a placeholder to avoid it run the default `yarn coverage` command
|
48
|
+
coverageCommand: ruby -v
|
42
49
|
debug: true
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.6.5
|
data/Gemfile.lock
CHANGED
@@ -1,39 +1,38 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tapping_device (0.
|
4
|
+
tapping_device (0.5.2)
|
5
5
|
activerecord (>= 5.2)
|
6
|
-
|
6
|
+
pry
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activemodel (6.0.
|
12
|
-
activesupport (= 6.0.
|
13
|
-
activerecord (6.0.
|
14
|
-
activemodel (= 6.0.
|
15
|
-
activesupport (= 6.0.
|
16
|
-
activesupport (6.0.
|
11
|
+
activemodel (6.0.3.1)
|
12
|
+
activesupport (= 6.0.3.1)
|
13
|
+
activerecord (6.0.3.1)
|
14
|
+
activemodel (= 6.0.3.1)
|
15
|
+
activesupport (= 6.0.3.1)
|
16
|
+
activesupport (6.0.3.1)
|
17
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
18
|
i18n (>= 0.7, < 2)
|
19
19
|
minitest (~> 5.1)
|
20
20
|
tzinfo (~> 1.1)
|
21
|
-
zeitwerk (~> 2.2)
|
22
|
-
|
23
|
-
|
24
|
-
concurrent-ruby (1.1.5)
|
21
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
22
|
+
coderay (1.1.3)
|
23
|
+
concurrent-ruby (1.1.6)
|
25
24
|
database_cleaner (1.7.0)
|
26
25
|
diff-lcs (1.3)
|
27
26
|
docile (1.3.2)
|
28
|
-
i18n (1.8.
|
27
|
+
i18n (1.8.3)
|
29
28
|
concurrent-ruby (~> 1.0)
|
30
29
|
json (2.3.0)
|
31
|
-
method_source (0.
|
32
|
-
minitest (5.14.
|
33
|
-
pry (0.
|
34
|
-
coderay (~> 1.1
|
35
|
-
method_source (~>
|
36
|
-
rake (
|
30
|
+
method_source (1.0.0)
|
31
|
+
minitest (5.14.1)
|
32
|
+
pry (0.13.1)
|
33
|
+
coderay (~> 1.1)
|
34
|
+
method_source (~> 1.0)
|
35
|
+
rake (13.0.1)
|
37
36
|
rspec (3.8.0)
|
38
37
|
rspec-core (~> 3.8.0)
|
39
38
|
rspec-expectations (~> 3.8.0)
|
@@ -54,9 +53,9 @@ GEM
|
|
54
53
|
simplecov-html (0.10.2)
|
55
54
|
sqlite3 (1.4.1)
|
56
55
|
thread_safe (0.3.6)
|
57
|
-
tzinfo (1.2.
|
56
|
+
tzinfo (1.2.7)
|
58
57
|
thread_safe (~> 0.1)
|
59
|
-
zeitwerk (2.
|
58
|
+
zeitwerk (2.3.0)
|
60
59
|
|
61
60
|
PLATFORMS
|
62
61
|
ruby
|
@@ -64,8 +63,7 @@ PLATFORMS
|
|
64
63
|
DEPENDENCIES
|
65
64
|
bundler (~> 2.0)
|
66
65
|
database_cleaner
|
67
|
-
|
68
|
-
rake (~> 10.0)
|
66
|
+
rake (~> 13.0)
|
69
67
|
rspec (~> 3.0)
|
70
68
|
simplecov (= 0.17.1)
|
71
69
|
sqlite3 (>= 1.3.6)
|
data/README.md
CHANGED
@@ -6,459 +6,241 @@
|
|
6
6
|
[](https://codeclimate.com/github/st0012/tapping_device/test_coverage)
|
7
7
|
[](https://www.codetriage.com/st0012/tapping_device)
|
8
8
|
|
9
|
-
## Related Posts
|
10
|
-
- [Debug Rails issues effectively with tapping_device](https://dev.to/st0012/debug-rails-issues-effectively-with-tappingdevice-c7c)
|
11
|
-
- [Want to know more about your Rails app? Tap on your objects!](https://dev.to/st0012/want-to-know-more-about-your-rails-app-tap-on-your-objects-bd3)
|
12
|
-
|
13
|
-
## Table of Content
|
14
|
-
- [Introduction](#introduction)
|
15
|
-
- [Print Object’s Traces](print-objects-traces)
|
16
|
-
- [Track Calls that Generates SQL Queries](#track-calls-that-generates-sql-queries)
|
17
|
-
- [Installation](#installation)
|
18
|
-
- [Usages](#usages)
|
19
|
-
- Tracing Helpers
|
20
|
-
- [print_traces](#print_traces)
|
21
|
-
- [print_calls_in_detail](#print_calls_in_detail)
|
22
|
-
- Tapping Methods
|
23
|
-
- [tap_init!](#tap_init)
|
24
|
-
- [tap_on!](#tap_on)
|
25
|
-
- [tap_passed!](#tap_passed)
|
26
|
-
- [tap_assoc!](#tap_assoc)
|
27
|
-
- [tap_sql!](#tap_sql)
|
28
|
-
- [Options](#options)
|
29
|
-
- [Payload](#payload-of-the-call)
|
30
|
-
- [Advance Usages](#advance-usages)
|
31
9
|
|
32
10
|
## Introduction
|
33
|
-
`
|
11
|
+
`TappingDevice` makes the objects tell you what they do, so you don't need to track them yourself.
|
34
12
|
|
35
|
-
|
36
|
-
|
37
|
-
Let your objects report to you, so you don’t need to guess how they work!
|
38
|
-
|
39
|
-
```ruby
|
40
|
-
class OrdersController < ApplicationController
|
41
|
-
include TappingDevice::Trackable
|
13
|
+
#### Contact Tracing For Objects
|
42
14
|
|
43
|
-
|
44
|
-
@cart = Cart.find(order_params[:cart_id])
|
45
|
-
print_traces(@cart, exclude_by_paths: [/gems/])
|
46
|
-
@order = OrderCreationService.new.perform(@cart)
|
47
|
-
end
|
48
|
-
```
|
15
|
+
The concept is very simple. It's basically like [contact tracing](https://en.wikipedia.org/wiki/Contact_tracing) for your Ruby objects. You can use
|
49
16
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
Called :reserved_until FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:18
|
54
|
-
Called :errors FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:9
|
55
|
-
Passed as 'cart' in 'OrderCreationService#apply_discount' at /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:10
|
56
|
-
Called :apply_discount FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:24
|
57
|
-
……
|
58
|
-
```
|
17
|
+
- `print_calls(object)` to see what the object does
|
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)
|
59
20
|
|
60
|
-
|
21
|
+
Still sounds vague? Let's see some examples:
|
61
22
|
|
62
|
-
###
|
23
|
+
### `print_calls` - Track Method Calls
|
63
24
|
|
64
|
-
|
25
|
+
In [Discourse](https://github.com/discourse/discourse), it uses the `Guardian` class for authorization (like policy objects). It's barely visible in controller actions, but it does many checks under the hood. Now, let's say we want to know what the `Guardian` would do when a user creates a post; here's the controller action:
|
65
26
|
|
66
27
|
```ruby
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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?)
|
76
46
|
end
|
77
47
|
end
|
78
|
-
end
|
79
|
-
```
|
80
|
-
|
81
|
-
```erb
|
82
|
-
<h1>Posts (<%= @posts.count %>)</h1>
|
83
|
-
......
|
84
|
-
<% @posts.each do |post| %>
|
85
|
-
......
|
86
|
-
<% end %>
|
87
|
-
......
|
88
|
-
<p>Posts created by you: <%= @posts.where(user: @current_user).count %></p>
|
89
|
-
```
|
90
|
-
|
91
|
-
And the output would be
|
92
|
-
|
93
|
-
```
|
94
|
-
Method: count generated sql: SELECT COUNT(*) FROM "posts" from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:3
|
95
|
-
Method: each generated sql: SELECT "posts".* FROM "posts" from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:16
|
96
|
-
Method: count generated sql: SELECT COUNT(*) FROM "posts" WHERE "posts"."user_id" = ? from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:31
|
97
48
|
```
|
98
49
|
|
99
|
-
|
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.
|
100
51
|
|
101
|
-
|
102
|
-
Add this line to your application's Gemfile:
|
52
|
+
But with `TappingDevice`. You can use `print_calls` to show what method calls the object performs
|
103
53
|
|
104
54
|
```ruby
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
$ bundle
|
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
|
+
# .....
|
112
61
|
```
|
113
62
|
|
114
|
-
|
63
|
+
Now, if you execute the code, like via tests:
|
115
64
|
|
65
|
+
```shell
|
66
|
+
$ rspec spec/requests/posts_controller_spec.rb:603
|
116
67
|
```
|
117
|
-
$ gem install tapping_device
|
118
|
-
```
|
119
|
-
|
120
|
-
## Usages
|
121
|
-
In order to use `tapping_device`, you need to include `TappingDevice::Trackable` module in where you want to track your code and call the following helpers.
|
122
68
|
|
123
|
-
|
69
|
+
You can get all the method calls it performs with basically everything you need to know
|
124
70
|
|
125
|
-
|
71
|
+
<img src="https://github.com/st0012/tapping_device/blob/master/images/print_calls.png" alt="image of print_calls output" width="50%">
|
126
72
|
|
127
|
-
|
128
|
-
|
129
|
-
|
73
|
+
Let's take a closer look at each entry. Everyone of them contains the method call's
|
74
|
+
- method name
|
75
|
+
- method source class/module
|
76
|
+
- call site
|
77
|
+
- arguments
|
78
|
+
- return value
|
130
79
|
|
131
|
-
|
132
|
-
@cart = Cart.find(order_params[:cart_id])
|
133
|
-
print_traces(@cart, exclude_by_paths: [/gems/])
|
134
|
-
@order = OrderCreationService.new.perform(@cart)
|
135
|
-
end
|
136
|
-
```
|
80
|
+

|
137
81
|
|
138
|
-
|
139
|
-
Passed as 'cart' in 'OrderCreationService#perform' at /Users/st0012/projects/tapping_device-demo/app/controllers/orders_controller.rb:10
|
140
|
-
Passed as 'cart' in 'OrderCreationService#validate_cart' at /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:8
|
141
|
-
Called :reserved_until FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:18
|
142
|
-
Called :errors FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:9
|
143
|
-
Passed as 'cart' in 'OrderCreationService#apply_discount' at /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:10
|
144
|
-
Called :apply_discount FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:24
|
145
|
-
……
|
146
|
-
```
|
82
|
+
These are the information you'd have to look up one by one manually (probably with many debug code writing). Now you can get all of them in just one line of code.
|
147
83
|
|
148
|
-
### print_calls_in_detail
|
149
84
|
|
150
|
-
|
85
|
+
### `print_traces` - See The Object's Traces
|
151
86
|
|
152
|
-
|
153
|
-
- `awesome_print:` - will print calls in prettier format if set to `true`. Default is `false`
|
87
|
+
If you're not interested in what an object does, but what it interacts with other parts of the program, e.g., used as arguments. You can use the `print_traces` helper. Let's see how `Discourse` uses the `manager` object when creating a post
|
154
88
|
|
155
89
|
```ruby
|
156
|
-
class OrdersController < ApplicationController
|
157
|
-
include TappingDevice::Trackable
|
158
|
-
|
159
90
|
def create
|
160
|
-
@
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
end
|
165
|
-
```
|
91
|
+
@manager_params = create_params
|
92
|
+
@manager_params[:first_post_checks] = !is_api?
|
93
|
+
|
94
|
+
manager = NewPostManager.new(current_user, @manager_params)
|
166
95
|
|
167
|
-
|
168
|
-
|
169
|
-
<= {:cart=>#<Cart id: 1, total: 10, customer_id: 1, promotion_id: nil, reserved_until: nil, created_at: "2020-01-05 09:48:28", updated_at: "2020-01-05 09:48:28">}
|
170
|
-
=> nil
|
171
|
-
FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:8
|
172
|
-
:apply_discount # OrderCreationService
|
173
|
-
<= {:cart=>#<Cart id: 1, total: 5, customer_id: 1, promotion_id: 1, reserved_until: nil, created_at: "2020-01-05 09:48:28", updated_at: "2020-01-05 09:48:28">, :promotion=>#<Promotion id: 1, amount: 0.5e1, customer_id: nil, created_at: "2020-01-05 09:48:28", updated_at: "2020-01-05 09:48:28">}
|
174
|
-
=> true
|
175
|
-
FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:10
|
176
|
-
:create_order # OrderCreationService
|
177
|
-
<= {:cart=>#<Cart id: 1, total: 5, customer_id: 1, promotion_id: 1, reserved_until: nil, created_at: "2020-01-05 09:48:28", updated_at: "2020-01-05 09:48:28">}
|
178
|
-
=> #<Order:0x00007f9ebcb17f08>
|
179
|
-
FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:11
|
180
|
-
:perform # OrderCreationService
|
181
|
-
<= {:cart=>#<Cart id: 1, total: 5, customer_id: 1, promotion_id: 1, reserved_until: nil, created_at: "2020-01-05 09:48:28", updated_at: "2020-01-05 09:48:28">, :promotion=>#<Promotion id: 1, amount: 0.5e1, customer_id: nil, created_at: "2020-01-05 09:48:28", updated_at: "2020-01-05 09:48:28">}
|
182
|
-
=> #<Order:0x00007f9ebcb17f08>
|
183
|
-
FROM /Users/st0012/projects/tapping_device-demo/app/controllers/orders_controller.rb:11
|
96
|
+
print_traces(manager)
|
97
|
+
# .....
|
184
98
|
```
|
185
99
|
|
186
|
-
|
100
|
+
And after running the test case
|
187
101
|
|
188
|
-
|
189
|
-
|
190
|
-
|
102
|
+
```shell
|
103
|
+
$ rspec spec/requests/posts_controller_spec.rb:603
|
104
|
+
```
|
191
105
|
|
192
|
-
|
193
|
-
calls = []
|
194
|
-
tap_init!(Student) do |payload|
|
195
|
-
calls << [payload[:method_name], payload[:arguments]]
|
196
|
-
end
|
106
|
+
You will see that it performs 2 calls: `perform` and `perform_create_post`. And it's also used as `manager` argument in various of calls of the `NewPostManager` class.
|
197
107
|
|
198
|
-
|
199
|
-
Student.new("Jane", 23)
|
108
|
+

|
200
109
|
|
201
|
-
|
202
|
-
```
|
110
|
+
### `print_mutations` - Display All State Changes At Once
|
203
111
|
|
204
|
-
|
112
|
+
Another thing that often bothers developers in debugging is to track an object's internal state changes. And `tapping_device` allows you to see all state changes with just one line of code. Let me keep using [Discourse](https://github.com/discourse/discourse) to demonstrate it.
|
205
113
|
|
206
|
-
|
114
|
+
When updating a post, it uses an object called `PostRevisor` to revise it:
|
207
115
|
|
208
116
|
```ruby
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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
|
+
# ......
|
216
124
|
end
|
217
125
|
end
|
218
126
|
```
|
219
127
|
|
220
|
-
|
221
|
-
|
222
|
-
```
|
223
|
-
name FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
|
224
|
-
user_id FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
|
225
|
-
to_param FROM /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
|
226
|
-
```
|
227
|
-
|
228
|
-
Also check the `track_as_records` option if you want to track `ActiveRecord` records.
|
229
|
-
|
230
|
-
### tap_passed!
|
231
|
-
|
232
|
-
`tap_passed!(target)` tracks method calls that **takes the target as its argument**. This is particularly useful when debugging libraries. It saves your time from jumping between files and check which path the object will go.
|
128
|
+
In the `PostReviser#revise!`, it uses many instance variables to track different information:
|
233
129
|
|
234
130
|
```ruby
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
@
|
240
|
-
|
241
|
-
tap_passed!(@post) do |payload|
|
242
|
-
puts(payload.passed_at(with_method_head: true))
|
243
|
-
end
|
244
|
-
end
|
245
|
-
end
|
246
|
-
```
|
247
|
-
|
248
|
-
```
|
249
|
-
Passed as 'record' in method ':polymorphic_mapping'
|
250
|
-
> def polymorphic_mapping(record)
|
251
|
-
at /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:131
|
252
|
-
Passed as 'klass' in method ':get_method_for_class'
|
253
|
-
> def get_method_for_class(klass)
|
254
|
-
at /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:269
|
255
|
-
Passed as 'record' in method ':handle_model'
|
256
|
-
> def handle_model(record)
|
257
|
-
at /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:227
|
258
|
-
Passed as 'record_or_hash_or_array' in method ':polymorphic_method'
|
259
|
-
> def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options)
|
260
|
-
at /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-6.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:139
|
261
|
-
```
|
131
|
+
# lib/post_revisor.rb
|
132
|
+
def revise!(editor, fields, opts = {})
|
133
|
+
@editor = editor
|
134
|
+
@fields = fields.with_indifferent_access
|
135
|
+
@opts = opts
|
262
136
|
|
263
|
-
|
137
|
+
@topic_changes = TopicChanges.new(@topic, editor)
|
138
|
+
|
139
|
+
# ......
|
264
140
|
|
265
|
-
|
141
|
+
@revised_at = @opts[:revised_at] || Time.now
|
142
|
+
@last_version_at = @post.last_version_at || Time.now
|
266
143
|
|
267
|
-
|
268
|
-
|
269
|
-
```
|
144
|
+
@version_changed = false
|
145
|
+
@post_successfully_saved = true
|
270
146
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
effective_line_items FROM /MY_PROJECT/app/models/line_item_container_helpers.rb:110
|
275
|
-
amending_orders FROM /MY_PROJECT/app/models/order.rb:385
|
276
|
-
amends_order FROM /MY_PROJECT/app/models/order.rb:432
|
147
|
+
@validate_post = true
|
148
|
+
# ......
|
149
|
+
end
|
277
150
|
```
|
278
151
|
|
279
|
-
|
152
|
+
Tracking the changes of that many instance variables can be a painful task, especially when we want to know the values before and after certain method call. This is why I created `print_mutations` to save us from this.
|
280
153
|
|
281
|
-
|
154
|
+
Like other helpers, you only need 1 line of code
|
282
155
|
|
283
156
|
```ruby
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
puts("Method: #{payload[:method_name]} generated sql: #{payload[:sql]} from #{payload[:filepath]}:#{payload[:line_number]}")
|
293
|
-
end
|
157
|
+
# app/controllers/posts_controller.rb
|
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
|
+
# ......
|
294
165
|
end
|
295
166
|
end
|
296
167
|
```
|
297
168
|
|
298
|
-
|
299
|
-
<h1>Posts (<%= @posts.count %>)</h1>
|
300
|
-
......
|
301
|
-
<% @posts.each do |post| %>
|
302
|
-
......
|
303
|
-
<% end %>
|
304
|
-
......
|
305
|
-
<p>Posts created by you: <%= @posts.where(user: @current_user).count %></p>
|
306
|
-
```
|
169
|
+
And then you'll see all the state changes:
|
307
170
|
|
308
|
-
|
309
|
-
Method: count generated sql: SELECT COUNT(*) FROM "posts" from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:3
|
310
|
-
Method: each generated sql: SELECT "posts".* FROM "posts" from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:16
|
311
|
-
Method: count generated sql: SELECT COUNT(*) FROM "posts" WHERE "posts"."user_id" = ? from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:31
|
312
|
-
```
|
171
|
+
<img src="https://github.com/st0012/tapping_device/blob/master/images/print_mutations.png" alt="image of print_mutations output" width="50%">
|
313
172
|
|
173
|
+
Now you can see what method changes which states. And more importantly, you get to see all the sate changes at once!
|
314
174
|
|
315
|
-
|
316
|
-
#### with_trace_to
|
317
|
-
It takes an integer as the number of traces we want to put into `trace`. Default is `nil`, so `trace` would be empty.
|
175
|
+
**You can try these examples on [my fork of discourse](https://github.com/st0012/discourse/tree/demo-for-tapping-device)**
|
318
176
|
|
319
|
-
```ruby
|
320
|
-
stan = Student.new("Stan", 18)
|
321
|
-
tap_on!(stan, with_trace_to: 5)
|
322
|
-
|
323
|
-
stan.name
|
324
|
-
|
325
|
-
puts(device.calls.first.trace) #=>
|
326
|
-
/Users/st0012/projects/tapping_device/spec/tapping_device_spec.rb:287:in `block (4 levels) in <top (required)>'
|
327
|
-
/Users/st0012/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/lib/rspec/core/example.rb:257:in `instance_exec'
|
328
|
-
/Users/st0012/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/lib/rspec/core/example.rb:257:in `block in run'
|
329
|
-
/Users/st0012/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/lib/rspec/core/example.rb:503:in `block in with_around_and_singleton_context_hooks'
|
330
|
-
/Users/st0012/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/lib/rspec/core/example.rb:460:in `block in with_around_example_hooks'
|
331
|
-
/Users/st0012/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/lib/rspec/core/hooks.rb:464:in `block in run'
|
332
|
-
```
|
333
177
|
|
334
|
-
|
335
|
-
|
178
|
+
## Installation
|
179
|
+
Add this line to your application's Gemfile:
|
336
180
|
|
337
181
|
```ruby
|
338
|
-
|
339
|
-
post = Post.find(@post.id) # same record but a different object
|
340
|
-
post.title #=> this call will be recorded as well
|
182
|
+
gem 'tapping_device', group: :development
|
341
183
|
```
|
342
184
|
|
343
|
-
|
344
|
-
It takes an array of call path patterns that we want to skip. This could be very helpful when working on a large project like Rails applications.
|
185
|
+
And then execute:
|
345
186
|
|
346
|
-
```ruby
|
347
|
-
tap_on!(@post, exclude_by_paths: [/active_record/]).and_print(:method_name_and_location)
|
348
187
|
```
|
349
|
-
|
188
|
+
$ bundle
|
350
189
|
```
|
351
|
-
_read_attribute FROM /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
|
352
|
-
name FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
|
353
|
-
_read_attribute FROM /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
|
354
|
-
user_id FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
|
355
|
-
.......
|
356
|
-
|
357
|
-
# versus
|
358
190
|
|
359
|
-
|
360
|
-
user_id FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
|
361
|
-
to_param FROM /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
|
362
|
-
```
|
191
|
+
Or install it directly:
|
363
192
|
|
364
|
-
#### filter_by_paths
|
365
|
-
|
366
|
-
Like `exclude_by_paths`, but work in the opposite way.
|
367
|
-
|
368
|
-
|
369
|
-
### Payload of The Call
|
370
|
-
All tapping methods (start with `tap_`) takes a block and yield a `Payload` object as a block argument. It responds to
|
371
|
-
|
372
|
-
- `target` - the target for `tap_x` call
|
373
|
-
- `receiver` - the receiver object
|
374
|
-
- `method_name` - method’s name (symbol)
|
375
|
-
- e.g. `:name`
|
376
|
-
- `method_object` - the method object that's being called. It might be `nil` in some edge cases.
|
377
|
-
- `arguments` - arguments of the method call
|
378
|
-
- e.g. `{name: “Stan”, age: 25}`
|
379
|
-
- `return_value` - return value of the method call
|
380
|
-
- `filepath` - path to the file that performs the method call
|
381
|
-
- `line_number`
|
382
|
-
- `defined_class` - in which class that defines the method being called
|
383
|
-
- `trace` - stack trace of the call. Default is an empty array unless `with_trace_to` option is set
|
384
|
-
- `sql` - sql that generated from the call (only present in `tap_sql!` payloads)
|
385
|
-
- `tp` - trace point object of this call
|
386
|
-
|
387
|
-
|
388
|
-
#### Symbols for Payload Helpers
|
389
|
-
- `FROM` for method call’s location
|
390
|
-
- `<=` for arguments
|
391
|
-
- `=>` for return value
|
392
|
-
- `@` for defined class
|
393
|
-
|
394
|
-
#### Payload Helpers
|
395
|
-
- `method_name_and_location` - `initialize FROM /PROJECT_PATH/tapping_device/spec/payload_spec.rb:7`
|
396
|
-
- `method_name_and_arguments` - `initialize <= {:name=>\"Stan\", :age=>25}`
|
397
|
-
- `method_name_and_return_value` - `ten => 10`
|
398
|
-
- `method_name_and_defined_class` - `initialize @ Student`
|
399
|
-
- `passed_at` -
|
400
193
|
```
|
401
|
-
|
402
|
-
at /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionview-6.0.0/lib/action_view/helpers/tags/label.rb:60
|
194
|
+
$ gem install tapping_device
|
403
195
|
```
|
404
196
|
|
405
|
-
|
197
|
+
**Depending on the size of your application, `TappingDevice` could harm the performance significantly. So make sure you don't put it inside the production group**
|
406
198
|
|
407
|
-
```
|
408
|
-
Passed as 'object' in method ':initialize'
|
409
|
-
> def initialize(template_object, object_name, method_name, object, tag_value)
|
410
|
-
at /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionview-6.0.0/lib/action_view/helpers/tags/label.rb:60
|
411
|
-
```
|
412
199
|
|
413
|
-
|
200
|
+
### Advance Usages & Options
|
414
201
|
|
202
|
+
#### Add Conditions With `.with`
|
203
|
+
|
204
|
+
Sometimes we don't need to know all the calls or traces of an object; we just want some of them. In those cases, we can chain the helpers with `.with` to filter the calls/traces.
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
# only prints calls with name matches /foo/
|
208
|
+
print_calls(object).with do |payload|
|
209
|
+
payload.method_name.to_s.match?(/foo/)
|
210
|
+
end
|
415
211
|
```
|
416
|
-
initialize @ Student
|
417
|
-
<= {:name=>"Stan", :age=>25}
|
418
|
-
=> 25
|
419
|
-
FROM /Users/st0012/projects/tapping_device/spec/payload_spec.rb:7
|
420
|
-
```
|
421
212
|
|
422
|
-
|
213
|
+
#### `colorize: false`
|
214
|
+
|
215
|
+
By default `print_calls` and `print_traces` colorize their output. If you don't want the colors, you can use `colorize: false` to disable it.
|
423
216
|
|
424
|
-
Tapping methods introduced above like `tap_on!` are designed for simple use cases. They're actually short for
|
425
217
|
|
426
218
|
```ruby
|
427
|
-
|
428
|
-
device.tap_on!(object)
|
219
|
+
print_calls(object, colorize: false)
|
429
220
|
```
|
430
221
|
|
431
|
-
And if you want to do some more configurations like stopping them manually or setting stop condition, you must have a `TappingDevie` instance. You can either get them like the above code or save the return value of `tap_*!` method calls.
|
432
222
|
|
433
|
-
####
|
223
|
+
#### `inspect: true`
|
434
224
|
|
435
|
-
|
436
|
-
1. Manually calling `device.stop!`
|
437
|
-
2. Setting stop condition with `device.stop_when`, like
|
225
|
+
As you might have noticed, all the objects are converted into strings with `#to_s` instead of `#inspect`. This is because when used on some Rails objects, `#inspect` can generate a significantly larger string than `#to_s`. For example:
|
438
226
|
|
439
|
-
```ruby
|
440
|
-
|
441
|
-
|
442
|
-
end
|
227
|
+
``` ruby
|
228
|
+
post.to_s #=> #<Post:0x00007f89a55201d0>
|
229
|
+
post.inspect #=> #<Post id: 649, user_id: 3, topic_id: 600, post_number: 1, raw: "Hello world", cooked: "<p>Hello world</p>", created_at: "2020-05-24 08:07:29", updated_at: "2020-05-24 08:07:29", reply_to_post_number: nil, reply_count: 0, quote_count: 0, deleted_at: nil, off_topic_count: 0, like_count: 0, incoming_link_count: 0, bookmark_count: 0, score: nil, reads: 0, post_type: 1, sort_order: 1, last_editor_id: 3, hidden: false, hidden_reason_id: nil, notify_moderators_count: 0, spam_count: 0, illegal_count: 0, inappropriate_count: 0, last_version_at: "2020-05-24 08:07:29", user_deleted: false, reply_to_user_id: nil, percent_rank: 1.0, notify_user_count: 0, like_score: 0, deleted_by_id: nil, edit_reason: nil, word_count: 2, version: 1, cook_method: 1, wiki: false, baked_at: "2020-05-24 08:07:29", baked_version: 2, hidden_at: nil, self_edits: 0, reply_quoted: false, via_email: false, raw_email: nil, public_version: 1, action_code: nil, image_url: nil, locked_by_id: nil, image_upload_id: nil>
|
443
230
|
```
|
444
231
|
|
445
|
-
#### Device states & Managing Devices
|
446
232
|
|
447
|
-
|
233
|
+
### Lower-Level Helpers
|
234
|
+
`print_calls` and `print_traces` aren't the only helpers you can get from `TappingDevice`. They are actually built on top of other helpers, which you can use as well. To know more about them, please check [this page](https://github.com/st0012/tapping_device/wiki/Advance-Usages)
|
448
235
|
|
449
|
-
- `Initial` - means the instance is initialized but hasn't tapped on anything.
|
450
|
-
- `Enabled` - means the instance is tapping on something (has called `tap_*` methods).
|
451
|
-
- `Disabled` - means the instance has been disabled. It will no longer receive any call info.
|
452
236
|
|
453
|
-
|
237
|
+
### Related Blog Posts
|
238
|
+
- [Optimize Your Debugging Process With Object-Oriented Tracing and tapping_device](http://bit.ly/object-oriented-tracing)
|
239
|
+
- [Debug Rails issues effectively with tapping_device](https://dev.to/st0012/debug-rails-issues-effectively-with-tappingdevice-c7c)
|
240
|
+
- [Want to know more about your Rails app? Tap on your objects!](https://dev.to/st0012/want-to-know-more-about-your-rails-app-tap-on-your-objects-bd3)
|
454
241
|
|
455
|
-
- `TappingDevice.devices` - Lists all registered devices with `initial` or `enabled` state. Note that any instance that's been stopped will be removed from the list.
|
456
|
-
- `TappingDevice.stop_all!` - Stops all registered devices and remove them from the `devices` list.
|
457
|
-
- `TappingDevice.suspend_new!` - Suspends any device instance from changing their state from `initial` to `enabled`. Which means any `tap_*` calls after it will no longer work.
|
458
|
-
- `TappingDevice.reset!` - Cancels `suspend_new` (if called) and stops/removes all created devices. Useful to reset the environment between test cases.
|
459
242
|
|
460
243
|
## Development
|
461
|
-
|
462
244
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
463
245
|
|
464
246
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
@@ -474,3 +256,4 @@ The gem is available as open-source under the terms of the [MIT License](https:/
|
|
474
256
|
## Code of Conduct
|
475
257
|
|
476
258
|
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).
|
259
|
+
|