tapping_device 0.4.8 → 0.5.1
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 +12 -5
- data/.ruby-version +1 -0
- data/Gemfile.lock +15 -15
- data/README.md +90 -371
- data/images/print_calls - single entry.png +0 -0
- data/images/print_calls.png +0 -0
- data/images/print_traces.png +0 -0
- data/lib/tapping_device.rb +57 -22
- data/lib/tapping_device/output_payload.rb +145 -0
- data/lib/tapping_device/payload.rb +2 -34
- data/lib/tapping_device/trackable.rb +31 -10
- data/lib/tapping_device/version.rb +1 -1
- data/tapping_device.gemspec +3 -3
- metadata +14 -7
- 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: 1b44a9cf6d9eb0e17b965c87b38f5c431058c283c2a6b79dcce1d2b88aafcc5e
|
4
|
+
data.tar.gz: 6afad055a9606958c47534cfa68394bc859c465300ac2c3f9959335edc0b7f4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e36715dc9d885af5b143ace2b5c3af6ff782e49d371e4b24787ac475f58d140abbad1a9730769722a59c2e06240a3a72930d9588f488595eaa6dc75973a17a31
|
7
|
+
data.tar.gz: e3bd4a46af607281dc82e6bb4e3f57ad54f600d99a25892d7914c4e5577dcf8a3dc212d60449e9ced5b1789680eead0f79f3758ef6b06c25b1b7647f52e2282e
|
data/.DS_Store
ADDED
Binary file
|
data/.github/workflows/ruby.yml
CHANGED
@@ -22,7 +22,7 @@ jobs:
|
|
22
22
|
- name: Install sqlite
|
23
23
|
run: |
|
24
24
|
# See https://github.community/t5/GitHub-Actions/ubuntu-latest-Apt-repository-list-issues/td-p/41122/page/2
|
25
|
-
|
25
|
+
for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done
|
26
26
|
sudo apt-get update
|
27
27
|
sudo apt-get install libsqlite3-dev
|
28
28
|
|
@@ -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,37 +1,37 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tapping_device (0.
|
4
|
+
tapping_device (0.5.1)
|
5
5
|
activerecord (>= 5.2)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activemodel (6.0.
|
11
|
-
activesupport (= 6.0.
|
12
|
-
activerecord (6.0.
|
13
|
-
activemodel (= 6.0.
|
14
|
-
activesupport (= 6.0.
|
15
|
-
activesupport (6.0.
|
10
|
+
activemodel (6.0.3.1)
|
11
|
+
activesupport (= 6.0.3.1)
|
12
|
+
activerecord (6.0.3.1)
|
13
|
+
activemodel (= 6.0.3.1)
|
14
|
+
activesupport (= 6.0.3.1)
|
15
|
+
activesupport (6.0.3.1)
|
16
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
17
|
i18n (>= 0.7, < 2)
|
18
18
|
minitest (~> 5.1)
|
19
19
|
tzinfo (~> 1.1)
|
20
|
-
zeitwerk (~> 2.2)
|
20
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
21
21
|
coderay (1.1.2)
|
22
|
-
concurrent-ruby (1.1.
|
22
|
+
concurrent-ruby (1.1.6)
|
23
23
|
database_cleaner (1.7.0)
|
24
24
|
diff-lcs (1.3)
|
25
25
|
docile (1.3.2)
|
26
|
-
i18n (1.
|
26
|
+
i18n (1.8.3)
|
27
27
|
concurrent-ruby (~> 1.0)
|
28
28
|
json (2.3.0)
|
29
29
|
method_source (0.9.2)
|
30
|
-
minitest (5.
|
30
|
+
minitest (5.14.1)
|
31
31
|
pry (0.12.2)
|
32
32
|
coderay (~> 1.1.0)
|
33
33
|
method_source (~> 0.9.0)
|
34
|
-
rake (
|
34
|
+
rake (13.0.1)
|
35
35
|
rspec (3.8.0)
|
36
36
|
rspec-core (~> 3.8.0)
|
37
37
|
rspec-expectations (~> 3.8.0)
|
@@ -52,9 +52,9 @@ GEM
|
|
52
52
|
simplecov-html (0.10.2)
|
53
53
|
sqlite3 (1.4.1)
|
54
54
|
thread_safe (0.3.6)
|
55
|
-
tzinfo (1.2.
|
55
|
+
tzinfo (1.2.7)
|
56
56
|
thread_safe (~> 0.1)
|
57
|
-
zeitwerk (2.
|
57
|
+
zeitwerk (2.3.0)
|
58
58
|
|
59
59
|
PLATFORMS
|
60
60
|
ruby
|
@@ -63,7 +63,7 @@ DEPENDENCIES
|
|
63
63
|
bundler (~> 2.0)
|
64
64
|
database_cleaner
|
65
65
|
pry
|
66
|
-
rake (~>
|
66
|
+
rake (~> 13.0)
|
67
67
|
rspec (~> 3.0)
|
68
68
|
simplecov (= 0.17.1)
|
69
69
|
sqlite3 (>= 1.3.6)
|
data/README.md
CHANGED
@@ -6,456 +6,175 @@
|
|
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
|
-
`
|
34
|
-
|
35
|
-
### Print Object’s Traces
|
11
|
+
`TappingDevice` makes the objects tell you what they do, so you don't need to track them yourself.
|
36
12
|
|
37
|
-
|
13
|
+
#### Contract Tracing For Objects
|
38
14
|
|
39
|
-
|
40
|
-
class OrdersController < ApplicationController
|
41
|
-
include TappingDevice::Trackable
|
42
|
-
|
43
|
-
def create
|
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
|
-
Passed as 'cart' in 'OrderCreationService#validate_cart' at /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:8
|
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)
|
59
19
|
|
60
|
-
|
20
|
+
Still sounds vague? Let's see some examples:
|
61
21
|
|
62
|
-
###
|
22
|
+
### `print_calls` To Track Method Calls
|
63
23
|
|
64
|
-
|
24
|
+
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
25
|
|
66
26
|
```ruby
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
27
|
+
def create
|
28
|
+
@manager_params = create_params
|
29
|
+
@manager_params[:first_post_checks] = !is_api?
|
30
|
+
|
31
|
+
manager = NewPostManager.new(current_user, @manager_params)
|
32
|
+
|
33
|
+
if is_api?
|
34
|
+
memoized_payload = DistributedMemoizer.memoize(signature_for(@manager_params), 120) do
|
35
|
+
result = manager.perform
|
36
|
+
MultiJson.dump(serialize_data(result, NewPostResultSerializer, root: false))
|
37
|
+
end
|
38
|
+
|
39
|
+
parsed_payload = JSON.parse(memoized_payload)
|
40
|
+
backwards_compatible_json(parsed_payload, parsed_payload['success'])
|
41
|
+
else
|
42
|
+
result = manager.perform
|
43
|
+
json = serialize_data(result, NewPostResultSerializer, root: false)
|
44
|
+
backwards_compatible_json(json, result.success?)
|
76
45
|
end
|
77
46
|
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
47
|
```
|
90
48
|
|
91
|
-
|
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
|
-
```
|
49
|
+
As you can see, it doesn't even exist in the controller action, which makes tracking it by reading code very hard to do.
|
98
50
|
|
99
|
-
|
100
|
-
|
101
|
-
## Installation
|
102
|
-
Add this line to your application's Gemfile:
|
51
|
+
But with `TappingDevice`. You can use `print_calls` to show what method calls the object performs
|
103
52
|
|
104
53
|
```ruby
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
$ bundle
|
54
|
+
def create
|
55
|
+
# you can retrieve the current guardian object by calling guardian in the controller
|
56
|
+
print_calls(guardian)
|
57
|
+
@manager_params = create_params
|
58
|
+
|
59
|
+
# .....
|
112
60
|
```
|
113
61
|
|
114
|
-
|
62
|
+
Now, if you execute the code, like via tests:
|
115
63
|
|
116
|
-
```
|
117
|
-
$
|
64
|
+
```shell
|
65
|
+
$ rspec spec/requests/posts_controller_spec.rb:603
|
118
66
|
```
|
119
67
|
|
120
|
-
|
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.
|
68
|
+
You can get all the method calls it performs with basically everything you need to know
|
122
69
|
|
123
|
-
|
70
|
+
<img src="https://github.com/st0012/tapping_device/blob/master/images/print_calls.png" alt="image of print_calls output" width="50%">
|
124
71
|
|
125
|
-
|
72
|
+
Let's take a closer look at each entry. Everyone of them contains the method call's
|
73
|
+
- method name
|
74
|
+
- method source class/module
|
75
|
+
- call site
|
76
|
+
- arguments
|
77
|
+
- return value
|
126
78
|
|
127
|
-
|
128
|
-
class OrdersController < ApplicationController
|
129
|
-
include TappingDevice::Trackable
|
79
|
+

|
130
80
|
|
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
|
-
```
|
81
|
+
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.
|
137
82
|
|
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
|
-
```
|
147
83
|
|
148
|
-
###
|
84
|
+
### `print_traces` To See The Object's Traces
|
149
85
|
|
150
|
-
|
86
|
+
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
|
151
87
|
|
152
88
|
```ruby
|
153
|
-
class OrdersController < ApplicationController
|
154
|
-
include TappingDevice::Trackable
|
155
|
-
|
156
89
|
def create
|
157
|
-
@
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
end
|
162
|
-
```
|
90
|
+
@manager_params = create_params
|
91
|
+
@manager_params[:first_post_checks] = !is_api?
|
92
|
+
|
93
|
+
manager = NewPostManager.new(current_user, @manager_params)
|
163
94
|
|
95
|
+
print_traces(manager)
|
96
|
+
# .....
|
164
97
|
```
|
165
|
-
:validate_cart # OrderCreationService
|
166
|
-
<= {: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">}
|
167
|
-
=> nil
|
168
|
-
FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:8
|
169
|
-
:apply_discount # OrderCreationService
|
170
|
-
<= {: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">}
|
171
|
-
=> true
|
172
|
-
FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:10
|
173
|
-
:create_order # OrderCreationService
|
174
|
-
<= {: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">}
|
175
|
-
=> #<Order:0x00007f9ebcb17f08>
|
176
|
-
FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:11
|
177
|
-
:perform # OrderCreationService
|
178
|
-
<= {: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">}
|
179
|
-
=> #<Order:0x00007f9ebcb17f08>
|
180
|
-
FROM /Users/st0012/projects/tapping_device-demo/app/controllers/orders_controller.rb:11
|
181
|
-
```
|
182
|
-
|
183
|
-
The output's order might look strange. This is because tapping_device needs to wait for the call to return in order to have its return value.
|
184
98
|
|
185
|
-
|
99
|
+
And after running the test case
|
186
100
|
|
187
|
-
|
188
|
-
|
189
|
-
```ruby
|
190
|
-
calls = []
|
191
|
-
tap_init!(Student) do |payload|
|
192
|
-
calls << [payload[:method_name], payload[:arguments]]
|
193
|
-
end
|
194
|
-
|
195
|
-
Student.new("Stan", 18)
|
196
|
-
Student.new("Jane", 23)
|
197
|
-
|
198
|
-
puts(calls.to_s) #=> [[:initialize, {:name=>"Stan", :age=>18}], [:initialize, {:name=>"Jane", :age=>23}]]
|
101
|
+
```shell
|
102
|
+
$ rspec spec/requests/posts_controller_spec.rb:603
|
199
103
|
```
|
200
104
|
|
201
|
-
|
105
|
+
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.
|
202
106
|
|
203
|
-
|
107
|
+

|
204
108
|
|
205
|
-
|
206
|
-
class PostsController < ApplicationController
|
207
|
-
include TappingDevice::Trackable
|
109
|
+
**You can try these examples on [my fork of discourse](https://github.com/st0012/discourse/tree/demo-for-tapping-device)**
|
208
110
|
|
209
|
-
before_action :set_post, only: [:show, :edit, :update, :destroy]
|
210
111
|
|
211
|
-
|
212
|
-
|
213
|
-
end
|
214
|
-
end
|
215
|
-
```
|
216
|
-
|
217
|
-
And you can see these in log:
|
218
|
-
|
219
|
-
```
|
220
|
-
name FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
|
221
|
-
user_id FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
|
222
|
-
to_param FROM /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
|
223
|
-
```
|
224
|
-
|
225
|
-
Also check the `track_as_records` option if you want to track `ActiveRecord` records.
|
226
|
-
|
227
|
-
### tap_passed!
|
228
|
-
|
229
|
-
`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.
|
112
|
+
## Installation
|
113
|
+
Add this line to your application's Gemfile:
|
230
114
|
|
231
115
|
```ruby
|
232
|
-
|
233
|
-
include TappingDevice::Trackable
|
234
|
-
# GET /posts/new
|
235
|
-
def new
|
236
|
-
@post = Post.new
|
237
|
-
|
238
|
-
tap_passed!(@post) do |payload|
|
239
|
-
puts(payload.passed_at(with_method_head: true))
|
240
|
-
end
|
241
|
-
end
|
242
|
-
end
|
243
|
-
```
|
244
|
-
|
245
|
-
```
|
246
|
-
Passed as 'record' in method ':polymorphic_mapping'
|
247
|
-
> def polymorphic_mapping(record)
|
248
|
-
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
|
249
|
-
Passed as 'klass' in method ':get_method_for_class'
|
250
|
-
> def get_method_for_class(klass)
|
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:269
|
252
|
-
Passed as 'record' in method ':handle_model'
|
253
|
-
> def handle_model(record)
|
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:227
|
255
|
-
Passed as 'record_or_hash_or_array' in method ':polymorphic_method'
|
256
|
-
> def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options)
|
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:139
|
116
|
+
gem 'tapping_device', group: :development
|
258
117
|
```
|
259
118
|
|
260
|
-
|
261
|
-
|
262
|
-
`tap_assoc!(activerecord_object)` tracks association calls on a record, like `post.comments`
|
263
|
-
|
264
|
-
```ruby
|
265
|
-
tap_assoc!(order).and_print(:method_name_and_location)
|
266
|
-
```
|
119
|
+
And then execute:
|
267
120
|
|
268
121
|
```
|
269
|
-
|
270
|
-
line_items FROM /MY_PROJECT/app/models/line_item_container_helpers.rb:44
|
271
|
-
effective_line_items FROM /MY_PROJECT/app/models/line_item_container_helpers.rb:110
|
272
|
-
amending_orders FROM /MY_PROJECT/app/models/order.rb:385
|
273
|
-
amends_order FROM /MY_PROJECT/app/models/order.rb:432
|
274
|
-
```
|
275
|
-
|
276
|
-
### tap_sql!
|
277
|
-
|
278
|
-
`tap_sql!(anything_that_generates_sql_queries)` tracks sql queries generated from the target
|
279
|
-
|
280
|
-
```ruby
|
281
|
-
class PostsController < ApplicationController
|
282
|
-
def index
|
283
|
-
# simulate current_user
|
284
|
-
@current_user = User.last
|
285
|
-
# reusable ActiveRecord::Relation
|
286
|
-
@posts = Post.all
|
287
|
-
|
288
|
-
tap_sql!(@posts) do |payload|
|
289
|
-
puts("Method: #{payload[:method_name]} generated sql: #{payload[:sql]} from #{payload[:filepath]}:#{payload[:line_number]}")
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
122
|
+
$ bundle
|
293
123
|
```
|
294
124
|
|
295
|
-
|
296
|
-
<h1>Posts (<%= @posts.count %>)</h1>
|
297
|
-
......
|
298
|
-
<% @posts.each do |post| %>
|
299
|
-
......
|
300
|
-
<% end %>
|
301
|
-
......
|
302
|
-
<p>Posts created by you: <%= @posts.where(user: @current_user).count %></p>
|
303
|
-
```
|
125
|
+
Or install it directly:
|
304
126
|
|
305
127
|
```
|
306
|
-
|
307
|
-
Method: each generated sql: SELECT "posts".* FROM "posts" from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:16
|
308
|
-
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
|
128
|
+
$ gem install tapping_device
|
309
129
|
```
|
310
130
|
|
131
|
+
**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**
|
311
132
|
|
312
|
-
### Options
|
313
|
-
#### with_trace_to
|
314
|
-
It takes an integer as the number of traces we want to put into `trace`. Default is `nil`, so `trace` would be empty.
|
315
133
|
|
316
|
-
|
317
|
-
stan = Student.new("Stan", 18)
|
318
|
-
tap_on!(stan, with_trace_to: 5)
|
319
|
-
|
320
|
-
stan.name
|
321
|
-
|
322
|
-
puts(device.calls.first.trace) #=>
|
323
|
-
/Users/st0012/projects/tapping_device/spec/tapping_device_spec.rb:287:in `block (4 levels) in <top (required)>'
|
324
|
-
/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'
|
325
|
-
/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'
|
326
|
-
/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'
|
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:460:in `block in with_around_example_hooks'
|
328
|
-
/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'
|
329
|
-
```
|
134
|
+
### Advance Usages & Options
|
330
135
|
|
331
|
-
####
|
332
|
-
It makes the device to track objects as they are ActiveRecord instances. For example:
|
136
|
+
#### Add Conditions With `.with`
|
333
137
|
|
334
|
-
|
335
|
-
tap_on!(@post, track_as_records: true)
|
336
|
-
post = Post.find(@post.id) # same record but a different object
|
337
|
-
post.title #=> this call will be recorded as well
|
338
|
-
```
|
339
|
-
|
340
|
-
#### exclude_by_paths
|
341
|
-
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.
|
138
|
+
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.
|
342
139
|
|
343
140
|
```ruby
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
_read_attribute FROM /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
|
349
|
-
name FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
|
350
|
-
_read_attribute FROM /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
|
351
|
-
user_id FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
|
352
|
-
.......
|
353
|
-
|
354
|
-
# versus
|
355
|
-
|
356
|
-
name FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
|
357
|
-
user_id FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
|
358
|
-
to_param FROM /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
|
359
|
-
```
|
360
|
-
|
361
|
-
#### filter_by_paths
|
362
|
-
|
363
|
-
Like `exclude_by_paths`, but work in the opposite way.
|
364
|
-
|
365
|
-
|
366
|
-
### Payload of The Call
|
367
|
-
All tapping methods (start with `tap_`) takes a block and yield a `Payload` object as a block argument. It responds to
|
368
|
-
|
369
|
-
- `target` - the target for `tap_x` call
|
370
|
-
- `receiver` - the receiver object
|
371
|
-
- `method_name` - method’s name (symbol)
|
372
|
-
- e.g. `:name`
|
373
|
-
- `method_object` - the method object that's being called. It might be `nil` in some edge cases.
|
374
|
-
- `arguments` - arguments of the method call
|
375
|
-
- e.g. `{name: “Stan”, age: 25}`
|
376
|
-
- `return_value` - return value of the method call
|
377
|
-
- `filepath` - path to the file that performs the method call
|
378
|
-
- `line_number`
|
379
|
-
- `defined_class` - in which class that defines the method being called
|
380
|
-
- `trace` - stack trace of the call. Default is an empty array unless `with_trace_to` option is set
|
381
|
-
- `sql` - sql that generated from the call (only present in `tap_sql!` payloads)
|
382
|
-
- `tp` - trace point object of this call
|
383
|
-
|
384
|
-
|
385
|
-
#### Symbols for Payload Helpers
|
386
|
-
- `FROM` for method call’s location
|
387
|
-
- `<=` for arguments
|
388
|
-
- `=>` for return value
|
389
|
-
- `@` for defined class
|
390
|
-
|
391
|
-
#### Payload Helpers
|
392
|
-
- `method_name_and_location` - `initialize FROM /PROJECT_PATH/tapping_device/spec/payload_spec.rb:7`
|
393
|
-
- `method_name_and_arguments` - `initialize <= {:name=>\"Stan\", :age=>25}`
|
394
|
-
- `method_name_and_return_value` - `ten => 10`
|
395
|
-
- `method_name_and_defined_class` - `initialize @ Student`
|
396
|
-
- `passed_at` -
|
397
|
-
```
|
398
|
-
Passed as 'object' in method ':initialize'
|
399
|
-
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
|
400
|
-
```
|
401
|
-
|
402
|
-
You can also set `passed_at(with_method_head: true)` to see the method's head.
|
403
|
-
|
404
|
-
```
|
405
|
-
Passed as 'object' in method ':initialize'
|
406
|
-
> def initialize(template_object, object_name, method_name, object, tag_value)
|
407
|
-
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
|
141
|
+
# only prints calls with name matches /foo/
|
142
|
+
print_calls(object).with do |payload|
|
143
|
+
payload.method_name.to_s.match?(/foo/)
|
144
|
+
end
|
408
145
|
```
|
409
146
|
|
410
|
-
|
147
|
+
#### `colorize: false`
|
411
148
|
|
412
|
-
|
413
|
-
initialize @ Student
|
414
|
-
<= {:name=>"Stan", :age=>25}
|
415
|
-
=> 25
|
416
|
-
FROM /Users/st0012/projects/tapping_device/spec/payload_spec.rb:7
|
417
|
-
```
|
149
|
+
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.
|
418
150
|
|
419
|
-
### Advance Usages
|
420
|
-
|
421
|
-
Tapping methods introduced above like `tap_on!` are designed for simple use cases. They're actually short for
|
422
151
|
|
423
152
|
```ruby
|
424
|
-
|
425
|
-
device.tap_on!(object)
|
153
|
+
print_calls(object, colorize: false)
|
426
154
|
```
|
427
155
|
|
428
|
-
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.
|
429
156
|
|
430
|
-
####
|
157
|
+
#### `inspect: true`
|
431
158
|
|
432
|
-
|
433
|
-
1. Manually calling `device.stop!`
|
434
|
-
2. Setting stop condition with `device.stop_when`, like
|
159
|
+
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:
|
435
160
|
|
436
|
-
```ruby
|
437
|
-
|
438
|
-
|
439
|
-
end
|
161
|
+
``` ruby
|
162
|
+
post.to_s #=> #<Post:0x00007f89a55201d0>
|
163
|
+
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>
|
440
164
|
```
|
441
165
|
|
442
|
-
#### Device states & Managing Devices
|
443
166
|
|
444
|
-
|
167
|
+
### Lower-Level Helpers
|
168
|
+
`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)
|
445
169
|
|
446
|
-
- `Initial` - means the instance is initialized but hasn't tapped on anything.
|
447
|
-
- `Enabled` - means the instance is tapping on something (has called `tap_*` methods).
|
448
|
-
- `Disabled` - means the instance has been disabled. It will no longer receive any call info.
|
449
170
|
|
450
|
-
|
171
|
+
### Related Blog Posts
|
172
|
+
- [Optimize Your Debugging Process With Object-Oriented Tracing and tapping_device](http://bit.ly/object-oriented-tracing)
|
173
|
+
- [Debug Rails issues effectively with tapping_device](https://dev.to/st0012/debug-rails-issues-effectively-with-tappingdevice-c7c)
|
174
|
+
- [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)
|
451
175
|
|
452
|
-
- `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.
|
453
|
-
- `TappingDevice.stop_all!` - Stops all registered devices and remove them from the `devices` list.
|
454
|
-
- `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.
|
455
|
-
- `TappingDevice.reset!` - Cancels `suspend_new` (if called) and stops/removes all created devices. Useful to reset the environment between test cases.
|
456
176
|
|
457
177
|
## Development
|
458
|
-
|
459
178
|
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.
|
460
179
|
|
461
180
|
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).
|