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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24fe45432978253d77089abca47f6b85e23b704936b6919a9e484a76bd423997
4
- data.tar.gz: 2f57680abf620d45426c4d4208c4d812427f600b7913a3e305f5f44fdcf1fbb6
3
+ metadata.gz: 1b44a9cf6d9eb0e17b965c87b38f5c431058c283c2a6b79dcce1d2b88aafcc5e
4
+ data.tar.gz: 6afad055a9606958c47534cfa68394bc859c465300ac2c3f9959335edc0b7f4d
5
5
  SHA512:
6
- metadata.gz: a449ed382aee9df385a3d64b50dced35aebb0f590243ec399c7e05c95cba9557d9e4b03ed7b158e83bc5dc7b3f9d032bb19608cff1d3344749eae8538efefef0
7
- data.tar.gz: b6a75131cd536086df46aa9a624bc9ccebd61c0e83e26312dd3ad2c6e9257922f19dee6f2a98cfd8e9eef6b1b0581d8db1c96c4101ec2a90f00bef9ad3022e48
6
+ metadata.gz: e36715dc9d885af5b143ace2b5c3af6ff782e49d371e4b24787ac475f58d140abbad1a9730769722a59c2e06240a3a72930d9588f488595eaa6dc75973a17a31
7
+ data.tar.gz: e3bd4a46af607281dc82e6bb4e3f57ad54f600d99a25892d7914c4e5577dcf8a3dc212d60449e9ced5b1789680eead0f79f3758ef6b06c25b1b7647f52e2282e
Binary file
@@ -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
- sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list
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 }} and publish result
37
- uses: paambaati/codeclimate-action@v2.3.0
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
- coverageCommand: bundle exec rake
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
@@ -0,0 +1 @@
1
+ 2.6.5
@@ -1,37 +1,37 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tapping_device (0.4.8)
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.2.1)
11
- activesupport (= 6.0.2.1)
12
- activerecord (6.0.2.1)
13
- activemodel (= 6.0.2.1)
14
- activesupport (= 6.0.2.1)
15
- activesupport (6.0.2.1)
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.5)
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.7.0)
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.13.0)
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 (10.0.4)
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.6)
55
+ tzinfo (1.2.7)
56
56
  thread_safe (~> 0.1)
57
- zeitwerk (2.2.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 (~> 10.0)
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
  [![Test Coverage](https://api.codeclimate.com/v1/badges/3e3732a6983785bccdbd/test_coverage)](https://codeclimate.com/github/st0012/tapping_device/test_coverage)
7
7
  [![Open Source Helpers](https://www.codetriage.com/st0012/tapping_device/badges/users.svg)](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
- `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:
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
- Let your objects report to you, so you don’t need to guess how they work!
13
+ #### Contract Tracing For Objects
38
14
 
39
- ```ruby
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
- 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
- ……
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
- (Also see [print_calls_in_detail](#print_calls_in_detail))
20
+ Still sounds vague? Let's see some examples:
61
21
 
62
- ### Track Calls that Generates SQL Queries
22
+ ### `print_calls` To Track Method Calls
63
23
 
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.
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
- class PostsController < ApplicationController
68
- def index
69
- # simulate current_user
70
- @current_user = User.last
71
- # reusable ActiveRecord::Relation
72
- @posts = Post.all
73
-
74
- tap_sql!(@posts) do |payload|
75
- puts("Method: #{payload[:method_name]} generated sql: #{payload[:sql]} from #{payload[:filepath]}:#{payload[:line_number]}")
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
- 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
- ```
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
- However, depending on the size of your application, tapping any object could **harm the performance significantly**. **Don't use this on production**
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
- gem 'tapping_device', group: :development
106
- ```
107
-
108
- And then execute:
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
- Or install it yourself as:
62
+ Now, if you execute the code, like via tests:
115
63
 
116
- ```
117
- $ gem install tapping_device
64
+ ```shell
65
+ $ rspec spec/requests/posts_controller_spec.rb:603
118
66
  ```
119
67
 
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.
68
+ You can get all the method calls it performs with basically everything you need to know
122
69
 
123
- ### print_traces
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
- 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.
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
- ```ruby
128
- class OrdersController < ApplicationController
129
- include TappingDevice::Trackable
79
+ ![explanation of individual entry](https://github.com/st0012/tapping_device/blob/master/images/print_calls%20-%20single%20entry.png)
130
80
 
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
- ```
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
- ### print_calls_in_detail
84
+ ### `print_traces` To See The Object's Traces
149
85
 
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.
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
- @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
- ```
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
- ### tap_init!
99
+ And after running the test case
186
100
 
187
- `tap_init!(class)` - tracks a class’ instance initialization
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
- ### tap_on!
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
- `tap_on!(object)` - tracks any calls received by the object.
107
+ ![image of print_traces output](https://github.com/st0012/tapping_device/blob/master/images/print_traces.png)
204
108
 
205
- ```ruby
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
- def show
212
- tap_on!(@post).and_print(:method_name_and_location)
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
- class PostsController < ApplicationController
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
- ### tap_assoc!
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
- payments FROM /RUBY_PATH/gems/2.6.0/gems/jsonapi-resources-0.9.10/lib/jsonapi/resource.rb:124
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
- ```erb
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
- Method: count generated sql: SELECT COUNT(*) FROM "posts" from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:3
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
- ```ruby
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
- #### track_as_records
332
- It makes the device to track objects as they are ActiveRecord instances. For example:
136
+ #### Add Conditions With `.with`
333
137
 
334
- ```ruby
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
- tap_on!(@post, exclude_by_paths: [/active_record/]).and_print(:method_name_and_location)
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
- - `detail_call_info`
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
- device = TappingDevice.new { # tapping action }
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
- #### Stop tapping
157
+ #### `inspect: true`
431
158
 
432
- Once you have a `TappingDevice` instance in hand, you will be able to stop the tapping by
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
- device.stop_when do |payload|
438
- device.calls.count >= 10 # stop after gathering 10 calls’ data
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
- Each `TappingDevice` instance can have 3 states:
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
- 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:
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).