tapping_device 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4000e62db2d9672d87d133a5c6adeeaf5eed9d134b4bd8b9a948887ee2d06f48
4
- data.tar.gz: 477c9bf04c5a218aa8a23034929bcae7d0d52e663954380bfeb7b57f1290152c
3
+ metadata.gz: 16d62dfe61cf06c2f3932ed32bc3a5c20ca652c743a1a238f156ff738103357e
4
+ data.tar.gz: ee964e53de1c35e0b3bc1d6be28f0fb5417d7a51bff5e9f8a16cc355297e3fcf
5
5
  SHA512:
6
- metadata.gz: 629ce1239a852d1bc1e9b7585ad7746273a5722139a08b24a8712448af9fc3201915eb3d882d7eed5300afaba590ee880e3db103a9fa9825d5304613e278ad23
7
- data.tar.gz: f03e06164e74ceab8bbef4f0dec75795fab1866ba427dbcc570a4dac6bdf5a7ac3c166e0d4a2af7a6b042d1fd74cc9ad7465c960254fe6536258bd7fa1b81031
6
+ metadata.gz: 3b6fe7b5a903f3d315c8258c1b4d7b22b24688eed0b13c7a8d505418bfc9f4e19cb8b1ef77a2494fadea1f4152130bb7ae41f401cadd37751d979169a417f6dc
7
+ data.tar.gz: 2a865ef86a2161a5b932b677e0ab6a953b8db083589388c9d0e59ba40214ae607691df7ed020306023c87fb92d1514821f0cccd4826fc97764a47de19f3d61f9
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tapping_device (0.1.1)
4
+ tapping_device (0.3.0)
5
5
  activerecord (~> 5.2)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -2,17 +2,17 @@
2
2
 
3
3
  ![](https://github.com/st0012/tapping_device/workflows/Ruby/badge.svg)
4
4
 
5
- `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 for this gem is to make debugging Rails applications easier. For example, you can use it to see who calls you `Post` records
5
+ `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 for this gem is to make debugging Rails applications easier. For example, you can use it to see who calls your `Post` records
6
6
 
7
7
  ```ruby
8
8
  class PostsController < ApplicationController
9
+ include TappingDevice::Trackable
10
+
9
11
  def show
10
12
  @post = Post.find(params[:id])
11
-
12
- device = TappingDevice.new do |payload|
13
+ tap_on!(@post) do |payload|
13
14
  puts "Method: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
14
15
  end
15
- device.tap_on!(@post)
16
16
  end
17
17
  end
18
18
  ```
@@ -28,10 +28,9 @@ Method: to_param line: /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_di
28
28
  Or you can use `tap_assoc!`. This is very useful for tracking potential n+1 query calls, here’s a sample from my work
29
29
 
30
30
  ```ruby
31
- device = TappingDevice.new do |payload|
31
+ tap_assoc!(order) do |payload|
32
32
  puts "Assoc: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
33
33
  end
34
- device.tap_assoc!(order)
35
34
  ```
36
35
 
37
36
  ```
@@ -42,12 +41,12 @@ Assoc: amending_orders line: /MY_PROJECT/app/models/order.rb:385
42
41
  Assoc: amends_order line: /MY_PROJECT/app/models/order.rb:432
43
42
  ```
44
43
 
45
- However, depending on the size of your application, tapping any object could **harm the performance significantly**. **Don't use this on production**
44
+ However, depending on the size of your application, tapping any object could **harm the performance significantly**. **Dont use this on production**
46
45
 
47
46
 
48
47
  ## Installation
49
48
 
50
- Add this line to your application's Gemfile:
49
+ Add this line to your applications Gemfile:
51
50
 
52
51
  ```ruby
53
52
  gem 'tapping_device', group: :development
@@ -66,44 +65,14 @@ $ gem install tapping_device
66
65
  ```
67
66
 
68
67
  ## Usage
68
+ In order to use `tapping_device`, you need to include `TappingDevice::Trackable` module in where you want to track your code.
69
69
 
70
- ### Create a device object
71
- In order to tap on something, you need to first initialize a tapping device with a block that process the call info.
72
-
73
- ```ruby
74
- device = TappingDevice.new do |payload|
75
- if payload[:method_name].to_s.match?(/foo/)
76
- puts "Method: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
77
- end
78
- end
79
- ```
70
+ ### Methods
71
+ - `tap_init!(class)` - tracks a class’ instance initialization
72
+ - `tap_on!(object)` - tracks any calls received by the object
73
+ - `tap_assoc!(activerecord_object)` - tracks association calls on a record, like `post.comments`
80
74
 
81
- ### Performance issue and setup stop condition
82
-
83
- Because `tapping_device` is built upon `TracePoint`, which literally scans **every method call** on **every object**. Even if we filter out the calls we’re not interested in, just filtering out through those method calls takes time if your application isn’t a small one. So it’s very important to stop the tapping device at a certain point. You can do this in 2 ways:
84
-
85
- #### Use `device.stop_when(&block)` to set a stop condition
86
- To define a stop condition, you can use `stop_when` method.
87
-
88
- ```ruby
89
- device.stop_when do |payload|
90
- device.calls.count >= 10 # stop after gathering 10 calls' data
91
- end
92
- ```
93
-
94
- **If you don’t set a stop condition, you need to use tapping methods that has exclamation mark**, like `device.tap_on!(post)`.
95
-
96
- #### `device.stop!`
97
- If you don’t define a stop condition, you can also use `device.stop!` to stop it manually.
98
-
99
- ### Start tapping
100
-
101
- #### Methods
102
- - `TappingDevice#tap_init(class)` - tracks a class’ instance initialization
103
- - `TappingDevice#tap_on(object)` - tracks any calls received by the object
104
- - `TappingDevice#tap_assoc(activerecord_object)` - tracks association calls on a record, like `post.comments`
105
-
106
- #### Info of the call
75
+ ### Info of the call
107
76
  All tapping methods (start with `tap_`) takes a block and yield a hash as block argument.
108
77
 
109
78
  ```ruby
@@ -140,14 +109,34 @@ The hash contains
140
109
  - `exclude_by_paths: [/path/]` - an array of call path patterns that we want to skip. This could be very helpful when working on large project like Rails applications.
141
110
  - `filter_by_paths: [/path/]` - only contain calls from the specified paths
142
111
 
143
- ### `#tap_init`
112
+ ```ruby
113
+ tap_on!(@post, exclude_by_paths: [/active_record/]) do |payload|
114
+ puts "Method: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
115
+ end
116
+ ```
117
+
118
+ ```
119
+ Method: _read_attribute line: /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
120
+ Method: name line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
121
+ Method: _read_attribute line: /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
122
+ Method: user_id line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
123
+ .......
124
+
125
+ # versus
126
+
127
+ Method: name line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
128
+ Method: user_id line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
129
+ Method: to_param line: /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
130
+ ```
131
+
132
+
133
+ ### `#tap_init!`
144
134
 
145
135
  ```ruby
146
136
  calls = []
147
- device = TappingDevice.new do |payload|
137
+ tap_init!(Student) do |payload|
148
138
  calls << [payload[:method_name], payload[:arguments]]
149
139
  end
150
- device.tap_init!(Student)
151
140
 
152
141
  Student.new("Stan", 18)
153
142
  Student.new("Jane", 23)
@@ -159,16 +148,14 @@ puts(calls.to_s) #=> [[:initialize, [[:name, "Stan"], [:age, 18]]], [:initialize
159
148
 
160
149
  ```ruby
161
150
  class PostsController < ApplicationController
151
+ include TappingDevice::Trackable
152
+
162
153
  before_action :set_post, only: [:show, :edit, :update, :destroy]
163
154
 
164
- # GET /posts/1
165
- # GET /posts/1.json
166
155
  def show
167
- device = TappingDevice.new do |payload|
156
+ tap_on!(@post) do |payload|
168
157
  puts "Method: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
169
158
  end
170
-
171
- device.tap_on!(@post)
172
159
  end
173
160
  end
174
161
  ```
@@ -184,10 +171,9 @@ Method: to_param line: /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_di
184
171
  ### `tap_assoc!`
185
172
 
186
173
  ```ruby
187
- device = TappingDevice.new do |payload|
174
+ tap_assoc!(order) do |payload|
188
175
  puts "Assoc: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
189
176
  end
190
- device.tap_assoc!(order)
191
177
  ```
192
178
 
193
179
  ```
@@ -198,12 +184,35 @@ Assoc: amending_orders line: /MY_PROJECT/app/models/order.rb:385
198
184
  Assoc: amends_order line: /MY_PROJECT/app/models/order.rb:432
199
185
  ```
200
186
 
201
- ### Device states & Managing Devices
187
+ ### Advance Usages
188
+
189
+ Tapping methods introduced above like `tap_on!` are designed for simple use cases. They’re actually short for
190
+
191
+ ```ruby
192
+ device = TappingDevice.new { # tapping action }
193
+ device.tap_on!(object)
194
+ ```
195
+
196
+ 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.
197
+
198
+ #### Stop tapping
199
+
200
+ Once you have a `TappingDevice` instance in hand, you will be able to stop the tapping by
201
+ 1. Manually calling `device.stop!`
202
+ 2. Setting stop condition with `device.stop_when`, like
203
+
204
+ ```ruby
205
+ device.stop_when do |payload|
206
+ device.calls.count >= 10 # stop after gathering 10 calls’ data
207
+ end
208
+ ```
209
+
210
+ #### Device states & Managing Devices
202
211
 
203
- Every `TappingDevice` instance can have 3 states:
212
+ Each `TappingDevice` instance can have 3 states:
204
213
 
205
- - `Initial` - means the instance is initialized but hasn’t been used to tap on anything.
206
- - `Enabled` - means the instance has started to tap on something (has called `tap_*` methods).
214
+ - `Initial` - means the instance is initialized but hasn’t tapped on anything.
215
+ - `Enabled` - means the instance are tapping on something (has called `tap_*` methods).
207
216
  - `Disabled` - means the instance has been disabled. It will no longer receive any call info.
208
217
 
209
218
  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:
@@ -1,3 +1,3 @@
1
1
  class TappingDevice
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -5,7 +5,6 @@ require "tapping_device/exceptions"
5
5
 
6
6
  class TappingDevice
7
7
  CALLER_START_POINT = 2
8
- FORCE_STOP_WHEN_MESSAGE = "You must set stop_when condition before start tapping"
9
8
 
10
9
  attr_reader :options, :calls, :trace_point
11
10
 
@@ -61,21 +60,6 @@ class TappingDevice
61
60
  track(record, condition: :tap_associations?, block: @block, **@options)
62
61
  end
63
62
 
64
- def tap_init(klass)
65
- validate_tapping(__method__)
66
- tap_init!(klass)
67
- end
68
-
69
- def tap_on(object)
70
- validate_tapping(__method__)
71
- tap_on!(object)
72
- end
73
-
74
- def tap_assoc(record)
75
- validate_tapping(__method__)
76
- tap_assoc!(record)
77
- end
78
-
79
63
  def set_block(&block)
80
64
  @block = block
81
65
  end
@@ -90,18 +74,15 @@ class TappingDevice
90
74
 
91
75
  private
92
76
 
93
- def validate_tapping(method_name)
94
- unless @stop_when
95
- raise TappingDevice::Exception.new <<~ERROR
96
- You must set stop_when condition before calling #{method_name}. Or you can use #{method_name}! to force tapping.
97
- Tapping without stop condition can largely slow down or even halt your application, because it'll need to
98
- screen literally every call happened.
99
- ERROR
100
- end
101
- end
102
-
103
77
  def track(object, condition:, block:, with_trace_to: nil, exclude_by_paths: [], filter_by_paths: nil)
104
78
  @trace_point = TracePoint.new(:return) do |tp|
79
+ validation_params = {
80
+ receiver: tp.self,
81
+ method_name: tp.callee_id
82
+ }
83
+
84
+ if send(condition, object, validation_params)
85
+
105
86
  filepath, line_number = caller(CALLER_START_POINT).first.split(":")[0..1]
106
87
 
107
88
  # this needs to be placed upfront so we can exclude noise before doing more work
@@ -126,8 +107,6 @@ class TappingDevice
126
107
  }
127
108
 
128
109
  yield_parameters[:trace] = caller[CALLER_START_POINT..(CALLER_START_POINT + with_trace_to)] if with_trace_to
129
-
130
- if send(condition, object, yield_parameters)
131
110
  if @block
132
111
  @calls << block.call(yield_parameters)
133
112
  else
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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - st0012
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-02 00:00:00.000000000 Z
11
+ date: 2019-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord