tapping_device 0.4.7 → 0.4.8
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/Gemfile.lock +2 -2
- data/README.md +116 -61
- data/lib/tapping_device/payload.rb +2 -2
- data/lib/tapping_device/trackable.rb +18 -0
- data/lib/tapping_device/version.rb +1 -1
- data/lib/tapping_device.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24fe45432978253d77089abca47f6b85e23b704936b6919a9e484a76bd423997
|
4
|
+
data.tar.gz: 2f57680abf620d45426c4d4208c4d812427f600b7913a3e305f5f44fdcf1fbb6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a449ed382aee9df385a3d64b50dced35aebb0f590243ec399c7e05c95cba9557d9e4b03ed7b158e83bc5dc7b3f9d032bb19608cff1d3344749eae8538efefef0
|
7
|
+
data.tar.gz: b6a75131cd536086df46aa9a624bc9ccebd61c0e83e26312dd3ad2c6e9257922f19dee6f2a98cfd8e9eef6b1b0581d8db1c96c4101ec2a90f00bef9ad3022e48
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# TappingDevice
|
2
2
|
|
3
|
-

|
3
|
+

|
4
|
+
[](https://badge.fury.io/rb/tapping_device)
|
4
5
|
[](https://codeclimate.com/github/st0012/tapping_device/maintainability)
|
5
6
|
[](https://codeclimate.com/github/st0012/tapping_device/test_coverage)
|
6
7
|
[](https://www.codetriage.com/st0012/tapping_device)
|
@@ -11,64 +12,56 @@
|
|
11
12
|
|
12
13
|
## Table of Content
|
13
14
|
- [Introduction](#introduction)
|
14
|
-
|
15
|
-
|
16
|
-
- [Track Calls that Generates SQL Queries](#track-calls-that-generates-sql-queries)
|
15
|
+
- [Print Object’s Traces](print-objects-traces)
|
16
|
+
- [Track Calls that Generates SQL Queries](#track-calls-that-generates-sql-queries)
|
17
17
|
- [Installation](#installation)
|
18
18
|
- [Usages](#usages)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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)
|
28
31
|
|
29
32
|
## Introduction
|
30
|
-
`tapping_device` is a gem built on top of Ruby
|
33
|
+
`tapping_device` is a gem built on top of Ruby's `TracePoint` class that allows you to tap method calls of specified objects. The purpose of this gem is to make debugging Rails applications easier. Here are some sample usages:
|
31
34
|
|
32
|
-
###
|
35
|
+
### Print Object’s Traces
|
36
|
+
|
37
|
+
Let your objects report to you, so you don’t need to guess how they work!
|
33
38
|
|
34
39
|
```ruby
|
35
|
-
class
|
40
|
+
class OrdersController < ApplicationController
|
36
41
|
include TappingDevice::Trackable
|
37
42
|
|
38
|
-
def
|
39
|
-
@
|
40
|
-
|
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)
|
41
47
|
end
|
42
|
-
end
|
43
48
|
```
|
44
49
|
|
45
|
-
And you can see these in log:
|
46
|
-
|
47
50
|
```
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
Passed as 'cart' in 'OrderCreationService#perform' at /Users/st0012/projects/tapping_device-demo/app/controllers/orders_controller.rb:10
|
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
|
+
……
|
51
58
|
```
|
52
59
|
|
53
|
-
|
54
|
-
|
55
|
-
Or you can use `tap_assoc!`. This is very useful for tracking potential n+1 query calls, here’s a sample from my work
|
56
|
-
|
57
|
-
```ruby
|
58
|
-
tap_assoc!(order).and_print(:method_name_and_location)
|
59
|
-
```
|
60
|
-
|
61
|
-
```
|
62
|
-
payments FROM /RUBY_PATH/gems/2.6.0/gems/jsonapi-resources-0.9.10/lib/jsonapi/resource.rb:124
|
63
|
-
line_items FROM /MY_PROJECT/app/models/line_item_container_helpers.rb:44
|
64
|
-
effective_line_items FROM /MY_PROJECT/app/models/line_item_container_helpers.rb:110
|
65
|
-
amending_orders FROM /MY_PROJECT/app/models/order.rb:385
|
66
|
-
amends_order FROM /MY_PROJECT/app/models/order.rb:432
|
67
|
-
```
|
60
|
+
(Also see [print_calls_in_detail](#print_calls_in_detail))
|
68
61
|
|
69
62
|
### Track Calls that Generates SQL Queries
|
70
63
|
|
71
|
-
`tap_sql!` method helps you track which method calls generate
|
64
|
+
`tap_sql!` method helps you track which method calls to generate SQL queries. This is particularly helpful when tracking calls created from a reused `ActiveRecord::Relation` object.
|
72
65
|
|
73
66
|
```ruby
|
74
67
|
class PostsController < ApplicationController
|
@@ -103,10 +96,10 @@ Method: each generated sql: SELECT "posts".* FROM "posts" from /PROJECT_PATH/rai
|
|
103
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
|
104
97
|
```
|
105
98
|
|
106
|
-
However, depending on the size of your application, tapping any object could **harm the performance significantly**. **Don
|
99
|
+
However, depending on the size of your application, tapping any object could **harm the performance significantly**. **Don't use this on production**
|
107
100
|
|
108
101
|
## Installation
|
109
|
-
Add this line to your application
|
102
|
+
Add this line to your application's Gemfile:
|
110
103
|
|
111
104
|
```ruby
|
112
105
|
gem 'tapping_device', group: :development
|
@@ -127,6 +120,68 @@ $ gem install tapping_device
|
|
127
120
|
## Usages
|
128
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.
|
129
122
|
|
123
|
+
### print_traces
|
124
|
+
|
125
|
+
It prints the object's trace. It's like mounting a GPS tracker + a spy camera on your object, so you can inspect your program through the object's eyes.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
class OrdersController < ApplicationController
|
129
|
+
include TappingDevice::Trackable
|
130
|
+
|
131
|
+
def create
|
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
|
+
```
|
137
|
+
|
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
|
+
|
148
|
+
### print_calls_in_detail
|
149
|
+
|
150
|
+
It prints the object's calls in detail (including call location, arguments, and return value). It's useful for observing an object's behavior when debugging.
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
class OrdersController < ApplicationController
|
154
|
+
include TappingDevice::Trackable
|
155
|
+
|
156
|
+
def create
|
157
|
+
@cart = Cart.find(order_params[:cart_id])
|
158
|
+
service = OrderCreationService.new
|
159
|
+
print_calls_in_detail(service)
|
160
|
+
@order = service.perform(@cart)
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
164
|
+
```
|
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
|
+
|
130
185
|
### tap_init!
|
131
186
|
|
132
187
|
`tap_init!(class)` - tracks a class’ instance initialization
|
@@ -145,7 +200,7 @@ puts(calls.to_s) #=> [[:initialize, {:name=>"Stan", :age=>18}], [:initialize, {:
|
|
145
200
|
|
146
201
|
### tap_on!
|
147
202
|
|
148
|
-
`tap_on!(object)` - tracks any calls received by the object
|
203
|
+
`tap_on!(object)` - tracks any calls received by the object.
|
149
204
|
|
150
205
|
```ruby
|
151
206
|
class PostsController < ApplicationController
|
@@ -283,7 +338,7 @@ post.title #=> this call will be recorded as well
|
|
283
338
|
```
|
284
339
|
|
285
340
|
#### exclude_by_paths
|
286
|
-
It takes an array of call path patterns that we want to skip. This could be very helpful when working on large project like Rails applications.
|
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.
|
287
342
|
|
288
343
|
```ruby
|
289
344
|
tap_on!(@post, exclude_by_paths: [/active_record/]).and_print(:method_name_and_location)
|
@@ -305,19 +360,19 @@ to_param FROM /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/r
|
|
305
360
|
|
306
361
|
#### filter_by_paths
|
307
362
|
|
308
|
-
Like `exclude_by_paths`, but work in
|
363
|
+
Like `exclude_by_paths`, but work in the opposite way.
|
309
364
|
|
310
365
|
|
311
366
|
### Payload of The Call
|
312
|
-
All tapping methods (start with `tap_`) takes a block and yield a `Payload` object as block argument. It responds to
|
367
|
+
All tapping methods (start with `tap_`) takes a block and yield a `Payload` object as a block argument. It responds to
|
313
368
|
|
314
369
|
- `target` - the target for `tap_x` call
|
315
370
|
- `receiver` - the receiver object
|
316
371
|
- `method_name` - method’s name (symbol)
|
317
|
-
|
318
|
-
- `method_object` - the method object that
|
372
|
+
- e.g. `:name`
|
373
|
+
- `method_object` - the method object that's being called. It might be `nil` in some edge cases.
|
319
374
|
- `arguments` - arguments of the method call
|
320
|
-
|
375
|
+
- e.g. `{name: “Stan”, age: 25}`
|
321
376
|
- `return_value` - return value of the method call
|
322
377
|
- `filepath` - path to the file that performs the method call
|
323
378
|
- `line_number`
|
@@ -344,7 +399,7 @@ Passed as 'object' in method ':initialize'
|
|
344
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
|
345
400
|
```
|
346
401
|
|
347
|
-
You can also set `passed_at(with_method_head: true)` to see the method
|
402
|
+
You can also set `passed_at(with_method_head: true)` to see the method's head.
|
348
403
|
|
349
404
|
```
|
350
405
|
Passed as 'object' in method ':initialize'
|
@@ -363,14 +418,14 @@ initialize @ Student
|
|
363
418
|
|
364
419
|
### Advance Usages
|
365
420
|
|
366
|
-
Tapping methods introduced above like `tap_on!` are designed for simple use cases. They
|
421
|
+
Tapping methods introduced above like `tap_on!` are designed for simple use cases. They're actually short for
|
367
422
|
|
368
423
|
```ruby
|
369
424
|
device = TappingDevice.new { # tapping action }
|
370
425
|
device.tap_on!(object)
|
371
426
|
```
|
372
427
|
|
373
|
-
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
|
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.
|
374
429
|
|
375
430
|
#### Stop tapping
|
376
431
|
|
@@ -388,16 +443,16 @@ end
|
|
388
443
|
|
389
444
|
Each `TappingDevice` instance can have 3 states:
|
390
445
|
|
391
|
-
- `Initial` - means the instance is initialized but hasn
|
392
|
-
- `Enabled` - means the instance
|
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).
|
393
448
|
- `Disabled` - means the instance has been disabled. It will no longer receive any call info.
|
394
449
|
|
395
|
-
When debugging, we may create many device instances and tap objects in several places. Then it
|
450
|
+
When debugging, we may create many device instances and tap objects in several places. Then it'll be quite annoying to manage their states. So `TappingDevice` has several class methods that allows you to manage all `TappingDevice` instances:
|
396
451
|
|
397
|
-
- `TappingDevice.devices` - Lists all registered devices with `initial` or `enabled` state. Note that any instance that
|
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.
|
398
453
|
- `TappingDevice.stop_all!` - Stops all registered devices and remove them from the `devices` list.
|
399
|
-
- `TappingDevice.suspend_new!` - Suspends any device instance from changing their state from `
|
400
|
-
- `TappingDevice.reset!` - Cancels `suspend_new` (if called) and stops/removes all created devices. Useful to reset environment between test cases.
|
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.
|
401
456
|
|
402
457
|
## Development
|
403
458
|
|
@@ -411,8 +466,8 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/st0012
|
|
411
466
|
|
412
467
|
## License
|
413
468
|
|
414
|
-
The gem is available as open
|
469
|
+
The gem is available as open-source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
415
470
|
|
416
471
|
## Code of Conduct
|
417
472
|
|
418
|
-
Everyone interacting in the TappingDevice project
|
473
|
+
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).
|
@@ -42,12 +42,12 @@ class TappingDevice
|
|
42
42
|
sql: "QUERIES",
|
43
43
|
return_value: "=>",
|
44
44
|
arguments: "<=",
|
45
|
-
defined_class: "
|
45
|
+
defined_class: "#"
|
46
46
|
}
|
47
47
|
|
48
48
|
SYMBOLS.each do |name, symbol|
|
49
49
|
define_method "method_name_and_#{name}" do
|
50
|
-
"
|
50
|
+
":#{method_name} #{symbol} #{send(name)}"
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
@@ -6,6 +6,24 @@ class TappingDevice
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
+
def print_traces(target, options = {})
|
10
|
+
options[:event_type] = :call
|
11
|
+
|
12
|
+
device_1 = tap_on!(target, options) do |payload|
|
13
|
+
puts("Called #{payload.method_name_and_location}")
|
14
|
+
end
|
15
|
+
device_2 = tap_passed!(target, options) do |payload|
|
16
|
+
arg_name = payload.arguments.keys.detect { |k| payload.arguments[k] == target }
|
17
|
+
next unless arg_name
|
18
|
+
puts("Passed as '#{arg_name}' in '#{payload.defined_class}##{payload.method_name}' at #{payload.location}")
|
19
|
+
end
|
20
|
+
[device_1, device_2]
|
21
|
+
end
|
22
|
+
|
23
|
+
def print_calls_in_detail(target, options = {})
|
24
|
+
tap_on!(target, options).and_print(:detail_call_info)
|
25
|
+
end
|
26
|
+
|
9
27
|
def new_device(options, &block)
|
10
28
|
TappingDevice.new(options, &block)
|
11
29
|
end
|
data/lib/tapping_device.rb
CHANGED
@@ -83,7 +83,7 @@ class TappingDevice
|
|
83
83
|
|
84
84
|
def track(object, condition:)
|
85
85
|
@target = object
|
86
|
-
@trace_point = TracePoint.new(:
|
86
|
+
@trace_point = TracePoint.new(options[:event_type]) do |tp|
|
87
87
|
if send(condition, object, tp)
|
88
88
|
filepath, line_number = get_call_location(tp)
|
89
89
|
|
@@ -209,6 +209,7 @@ class TappingDevice
|
|
209
209
|
options[:exclude_by_paths] ||= []
|
210
210
|
options[:with_trace_to] ||= 50
|
211
211
|
options[:root_device] ||= self
|
212
|
+
options[:event_type] ||= :return
|
212
213
|
options[:descendants] ||= []
|
213
214
|
options[:track_as_records] ||= false
|
214
215
|
options
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tapping_device
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.8
|
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: 2020-01-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|