tapping_device 0.4.7 → 0.4.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
![](https://github.com/st0012/tapping_device/workflows/Ruby/badge.svg)
|
3
|
+
![GitHub Action](https://github.com/st0012/tapping_device/workflows/Ruby/badge.svg)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/tapping_device.svg)](https://badge.fury.io/rb/tapping_device)
|
4
5
|
[![Maintainability](https://api.codeclimate.com/v1/badges/3e3732a6983785bccdbd/maintainability)](https://codeclimate.com/github/st0012/tapping_device/maintainability)
|
5
6
|
[![Test Coverage](https://api.codeclimate.com/v1/badges/3e3732a6983785bccdbd/test_coverage)](https://codeclimate.com/github/st0012/tapping_device/test_coverage)
|
6
7
|
[![Open Source Helpers](https://www.codetriage.com/st0012/tapping_device/badges/users.svg)](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
|