tapping_device 0.4.9 → 0.5.2

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: ac2d7b19e16c5c1f3f09d8d1abb81f8efe1238366a056457a804adf8c3ed5746
4
- data.tar.gz: d683824d3c9ba64dc968d11891b2d1a5522768dbea68e604a6ed4dc04bef7488
3
+ metadata.gz: fbf652959b6f125f5e4ece39c59db0b8ebd55096e4aa639452de306857193bf7
4
+ data.tar.gz: 31d13f1f4f38b6009795e4d948ba4596428355cb03d113603b3801c307c14a18
5
5
  SHA512:
6
- metadata.gz: bae37829ceac31871f11da1b9f2be3000572e65291229eb1208794e4214dc28bc55e4cc2dd44ec4a2dc1fd6cf01798509d860f132a6503b47368eadb544f172a
7
- data.tar.gz: 73053c155178b21ddb93627f227f7a50130684b29c95cf0c310aedd06bcbf3ab94529a79fd73451725b7180e93ffa9e64b9d3fd7dd8e2c5125a15c8ea1500d21
6
+ metadata.gz: 98eae30679e7e08f7607dbd7ee457b9db9565e46ddf2859a782f2b5ce817bf014937b65f8ee0f342bb7d6dbec3afcb3aef980e396362726839c4031db03cb2e5
7
+ data.tar.gz: bf2d0e6e8e38466f928d57580edf745b1ee1ce18ed1ee03968dc76647b67cfb4e0feaeff0166ac7503c295c54351445344f828a0bc3878b57a74ed8a130a03cb
Binary file
@@ -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,39 +1,38 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tapping_device (0.4.9)
4
+ tapping_device (0.5.2)
5
5
  activerecord (>= 5.2)
6
- awesome_print
6
+ pry
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activemodel (6.0.2.1)
12
- activesupport (= 6.0.2.1)
13
- activerecord (6.0.2.1)
14
- activemodel (= 6.0.2.1)
15
- activesupport (= 6.0.2.1)
16
- activesupport (6.0.2.1)
11
+ activemodel (6.0.3.1)
12
+ activesupport (= 6.0.3.1)
13
+ activerecord (6.0.3.1)
14
+ activemodel (= 6.0.3.1)
15
+ activesupport (= 6.0.3.1)
16
+ activesupport (6.0.3.1)
17
17
  concurrent-ruby (~> 1.0, >= 1.0.2)
18
18
  i18n (>= 0.7, < 2)
19
19
  minitest (~> 5.1)
20
20
  tzinfo (~> 1.1)
21
- zeitwerk (~> 2.2)
22
- awesome_print (1.8.0)
23
- coderay (1.1.2)
24
- concurrent-ruby (1.1.5)
21
+ zeitwerk (~> 2.2, >= 2.2.2)
22
+ coderay (1.1.3)
23
+ concurrent-ruby (1.1.6)
25
24
  database_cleaner (1.7.0)
26
25
  diff-lcs (1.3)
27
26
  docile (1.3.2)
28
- i18n (1.8.2)
27
+ i18n (1.8.3)
29
28
  concurrent-ruby (~> 1.0)
30
29
  json (2.3.0)
31
- method_source (0.9.2)
32
- minitest (5.14.0)
33
- pry (0.12.2)
34
- coderay (~> 1.1.0)
35
- method_source (~> 0.9.0)
36
- rake (10.0.4)
30
+ method_source (1.0.0)
31
+ minitest (5.14.1)
32
+ pry (0.13.1)
33
+ coderay (~> 1.1)
34
+ method_source (~> 1.0)
35
+ rake (13.0.1)
37
36
  rspec (3.8.0)
38
37
  rspec-core (~> 3.8.0)
39
38
  rspec-expectations (~> 3.8.0)
@@ -54,9 +53,9 @@ GEM
54
53
  simplecov-html (0.10.2)
55
54
  sqlite3 (1.4.1)
56
55
  thread_safe (0.3.6)
57
- tzinfo (1.2.6)
56
+ tzinfo (1.2.7)
58
57
  thread_safe (~> 0.1)
59
- zeitwerk (2.2.2)
58
+ zeitwerk (2.3.0)
60
59
 
61
60
  PLATFORMS
62
61
  ruby
@@ -64,8 +63,7 @@ PLATFORMS
64
63
  DEPENDENCIES
65
64
  bundler (~> 2.0)
66
65
  database_cleaner
67
- pry
68
- rake (~> 10.0)
66
+ rake (~> 13.0)
69
67
  rspec (~> 3.0)
70
68
  simplecov (= 0.17.1)
71
69
  sqlite3 (>= 1.3.6)
data/README.md CHANGED
@@ -6,459 +6,241 @@
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:
11
+ `TappingDevice` makes the objects tell you what they do, so you don't need to track them yourself.
34
12
 
35
- ### Print Object’s Traces
36
-
37
- Let your objects report to you, so you don’t need to guess how they work!
38
-
39
- ```ruby
40
- class OrdersController < ApplicationController
41
- include TappingDevice::Trackable
13
+ #### Contact Tracing For Objects
42
14
 
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)
19
+ - `print_mutations(object)` to see what actions changed the object's state (instance variables)
59
20
 
60
- (Also see [print_calls_in_detail](#print_calls_in_detail))
21
+ Still sounds vague? Let's see some examples:
61
22
 
62
- ### Track Calls that Generates SQL Queries
23
+ ### `print_calls` - Track Method Calls
63
24
 
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.
25
+ 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
26
 
66
27
  ```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]}")
28
+ def create
29
+ @manager_params = create_params
30
+ @manager_params[:first_post_checks] = !is_api?
31
+
32
+ manager = NewPostManager.new(current_user, @manager_params)
33
+
34
+ if is_api?
35
+ memoized_payload = DistributedMemoizer.memoize(signature_for(@manager_params), 120) do
36
+ result = manager.perform
37
+ MultiJson.dump(serialize_data(result, NewPostResultSerializer, root: false))
38
+ end
39
+
40
+ parsed_payload = JSON.parse(memoized_payload)
41
+ backwards_compatible_json(parsed_payload, parsed_payload['success'])
42
+ else
43
+ result = manager.perform
44
+ json = serialize_data(result, NewPostResultSerializer, root: false)
45
+ backwards_compatible_json(json, result.success?)
76
46
  end
77
47
  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
- ```
90
-
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
48
  ```
98
49
 
99
- However, depending on the size of your application, tapping any object could **harm the performance significantly**. **Don't use this on production**
50
+ As you can see, it doesn't even exist in the controller action, which makes tracking it by reading code very hard to do.
100
51
 
101
- ## Installation
102
- Add this line to your application's Gemfile:
52
+ But with `TappingDevice`. You can use `print_calls` to show what method calls the object performs
103
53
 
104
54
  ```ruby
105
- gem 'tapping_device', group: :development
106
- ```
107
-
108
- And then execute:
109
-
110
- ```
111
- $ bundle
55
+ def create
56
+ # you can retrieve the current guardian object by calling guardian in the controller
57
+ print_calls(guardian)
58
+ @manager_params = create_params
59
+
60
+ # .....
112
61
  ```
113
62
 
114
- Or install it yourself as:
63
+ Now, if you execute the code, like via tests:
115
64
 
65
+ ```shell
66
+ $ rspec spec/requests/posts_controller_spec.rb:603
116
67
  ```
117
- $ gem install tapping_device
118
- ```
119
-
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.
122
68
 
123
- ### print_traces
69
+ You can get all the method calls it performs with basically everything you need to know
124
70
 
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.
71
+ <img src="https://github.com/st0012/tapping_device/blob/master/images/print_calls.png" alt="image of print_calls output" width="50%">
126
72
 
127
- ```ruby
128
- class OrdersController < ApplicationController
129
- include TappingDevice::Trackable
73
+ Let's take a closer look at each entry. Everyone of them contains the method call's
74
+ - method name
75
+ - method source class/module
76
+ - call site
77
+ - arguments
78
+ - return value
130
79
 
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
- ```
80
+ ![explanation of individual entry](https://github.com/st0012/tapping_device/blob/master/images/print_calls%20-%20single%20entry.png)
137
81
 
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
- ```
82
+ 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.
147
83
 
148
- ### print_calls_in_detail
149
84
 
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.
85
+ ### `print_traces` - See The Object's Traces
151
86
 
152
- #### Options
153
- - `awesome_print:` - will print calls in prettier format if set to `true`. Default is `false`
87
+ 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
154
88
 
155
89
  ```ruby
156
- class OrdersController < ApplicationController
157
- include TappingDevice::Trackable
158
-
159
90
  def create
160
- @cart = Cart.find(order_params[:cart_id])
161
- service = OrderCreationService.new
162
- print_calls_in_detail(service)
163
- @order = service.perform(@cart)
164
- end
165
- ```
91
+ @manager_params = create_params
92
+ @manager_params[:first_post_checks] = !is_api?
93
+
94
+ manager = NewPostManager.new(current_user, @manager_params)
166
95
 
167
- ```
168
- :validate_cart # OrderCreationService
169
- <= {: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">}
170
- => nil
171
- FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:8
172
- :apply_discount # OrderCreationService
173
- <= {: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">}
174
- => true
175
- FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:10
176
- :create_order # OrderCreationService
177
- <= {: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">}
178
- => #<Order:0x00007f9ebcb17f08>
179
- FROM /Users/st0012/projects/tapping_device-demo/app/services/order_creation_service.rb:11
180
- :perform # OrderCreationService
181
- <= {: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">}
182
- => #<Order:0x00007f9ebcb17f08>
183
- FROM /Users/st0012/projects/tapping_device-demo/app/controllers/orders_controller.rb:11
96
+ print_traces(manager)
97
+ # .....
184
98
  ```
185
99
 
186
- 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.
100
+ And after running the test case
187
101
 
188
- ### tap_init!
189
-
190
- `tap_init!(class)` - tracks a class’ instance initialization
102
+ ```shell
103
+ $ rspec spec/requests/posts_controller_spec.rb:603
104
+ ```
191
105
 
192
- ```ruby
193
- calls = []
194
- tap_init!(Student) do |payload|
195
- calls << [payload[:method_name], payload[:arguments]]
196
- end
106
+ 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.
197
107
 
198
- Student.new("Stan", 18)
199
- Student.new("Jane", 23)
108
+ ![image of print_traces output](https://github.com/st0012/tapping_device/blob/master/images/print_traces.png)
200
109
 
201
- puts(calls.to_s) #=> [[:initialize, {:name=>"Stan", :age=>18}], [:initialize, {:name=>"Jane", :age=>23}]]
202
- ```
110
+ ### `print_mutations` - Display All State Changes At Once
203
111
 
204
- ### tap_on!
112
+ Another thing that often bothers developers in debugging is to track an object's internal state changes. And `tapping_device` allows you to see all state changes with just one line of code. Let me keep using [Discourse](https://github.com/discourse/discourse) to demonstrate it.
205
113
 
206
- `tap_on!(object)` - tracks any calls received by the object.
114
+ When updating a post, it uses an object called `PostRevisor` to revise it:
207
115
 
208
116
  ```ruby
209
- class PostsController < ApplicationController
210
- include TappingDevice::Trackable
211
-
212
- before_action :set_post, only: [:show, :edit, :update, :destroy]
213
-
214
- def show
215
- tap_on!(@post).and_print(:method_name_and_location)
117
+ # app/controllers/posts_controller.rb
118
+ class PostsController
119
+ def update
120
+ # ......
121
+ revisor = PostRevisor.new(post, topic)
122
+ revisor.revise!(current_user, changes, opts)
123
+ # ......
216
124
  end
217
125
  end
218
126
  ```
219
127
 
220
- And you can see these in log:
221
-
222
- ```
223
- name FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
224
- user_id FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
225
- to_param FROM /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
226
- ```
227
-
228
- Also check the `track_as_records` option if you want to track `ActiveRecord` records.
229
-
230
- ### tap_passed!
231
-
232
- `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.
128
+ In the `PostReviser#revise!`, it uses many instance variables to track different information:
233
129
 
234
130
  ```ruby
235
- class PostsController < ApplicationController
236
- include TappingDevice::Trackable
237
- # GET /posts/new
238
- def new
239
- @post = Post.new
240
-
241
- tap_passed!(@post) do |payload|
242
- puts(payload.passed_at(with_method_head: true))
243
- end
244
- end
245
- end
246
- ```
247
-
248
- ```
249
- Passed as 'record' in method ':polymorphic_mapping'
250
- > def polymorphic_mapping(record)
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:131
252
- Passed as 'klass' in method ':get_method_for_class'
253
- > def get_method_for_class(klass)
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:269
255
- Passed as 'record' in method ':handle_model'
256
- > def handle_model(record)
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:227
258
- Passed as 'record_or_hash_or_array' in method ':polymorphic_method'
259
- > def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options)
260
- 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
261
- ```
131
+ # lib/post_revisor.rb
132
+ def revise!(editor, fields, opts = {})
133
+ @editor = editor
134
+ @fields = fields.with_indifferent_access
135
+ @opts = opts
262
136
 
263
- ### tap_assoc!
137
+ @topic_changes = TopicChanges.new(@topic, editor)
138
+
139
+ # ......
264
140
 
265
- `tap_assoc!(activerecord_object)` tracks association calls on a record, like `post.comments`
141
+ @revised_at = @opts[:revised_at] || Time.now
142
+ @last_version_at = @post.last_version_at || Time.now
266
143
 
267
- ```ruby
268
- tap_assoc!(order).and_print(:method_name_and_location)
269
- ```
144
+ @version_changed = false
145
+ @post_successfully_saved = true
270
146
 
271
- ```
272
- payments FROM /RUBY_PATH/gems/2.6.0/gems/jsonapi-resources-0.9.10/lib/jsonapi/resource.rb:124
273
- line_items FROM /MY_PROJECT/app/models/line_item_container_helpers.rb:44
274
- effective_line_items FROM /MY_PROJECT/app/models/line_item_container_helpers.rb:110
275
- amending_orders FROM /MY_PROJECT/app/models/order.rb:385
276
- amends_order FROM /MY_PROJECT/app/models/order.rb:432
147
+ @validate_post = true
148
+ # ......
149
+ end
277
150
  ```
278
151
 
279
- ### tap_sql!
152
+ Tracking the changes of that many instance variables can be a painful task, especially when we want to know the values before and after certain method call. This is why I created `print_mutations` to save us from this.
280
153
 
281
- `tap_sql!(anything_that_generates_sql_queries)` tracks sql queries generated from the target
154
+ Like other helpers, you only need 1 line of code
282
155
 
283
156
  ```ruby
284
- class PostsController < ApplicationController
285
- def index
286
- # simulate current_user
287
- @current_user = User.last
288
- # reusable ActiveRecord::Relation
289
- @posts = Post.all
290
-
291
- tap_sql!(@posts) do |payload|
292
- puts("Method: #{payload[:method_name]} generated sql: #{payload[:sql]} from #{payload[:filepath]}:#{payload[:line_number]}")
293
- end
157
+ # app/controllers/posts_controller.rb
158
+ class PostsController
159
+ def update
160
+ # ......
161
+ revisor = PostRevisor.new(post, topic)
162
+ print_mutations(revisor)
163
+ revisor.revise!(current_user, changes, opts)
164
+ # ......
294
165
  end
295
166
  end
296
167
  ```
297
168
 
298
- ```erb
299
- <h1>Posts (<%= @posts.count %>)</h1>
300
- ......
301
- <% @posts.each do |post| %>
302
- ......
303
- <% end %>
304
- ......
305
- <p>Posts created by you: <%= @posts.where(user: @current_user).count %></p>
306
- ```
169
+ And then you'll see all the state changes:
307
170
 
308
- ```
309
- Method: count generated sql: SELECT COUNT(*) FROM "posts" from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:3
310
- Method: each generated sql: SELECT "posts".* FROM "posts" from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:16
311
- 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
312
- ```
171
+ <img src="https://github.com/st0012/tapping_device/blob/master/images/print_mutations.png" alt="image of print_mutations output" width="50%">
313
172
 
173
+ Now you can see what method changes which states. And more importantly, you get to see all the sate changes at once!
314
174
 
315
- ### Options
316
- #### with_trace_to
317
- It takes an integer as the number of traces we want to put into `trace`. Default is `nil`, so `trace` would be empty.
175
+ **You can try these examples on [my fork of discourse](https://github.com/st0012/discourse/tree/demo-for-tapping-device)**
318
176
 
319
- ```ruby
320
- stan = Student.new("Stan", 18)
321
- tap_on!(stan, with_trace_to: 5)
322
-
323
- stan.name
324
-
325
- puts(device.calls.first.trace) #=>
326
- /Users/st0012/projects/tapping_device/spec/tapping_device_spec.rb:287:in `block (4 levels) in <top (required)>'
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:257:in `instance_exec'
328
- /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'
329
- /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'
330
- /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'
331
- /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'
332
- ```
333
177
 
334
- #### track_as_records
335
- It makes the device to track objects as they are ActiveRecord instances. For example:
178
+ ## Installation
179
+ Add this line to your application's Gemfile:
336
180
 
337
181
  ```ruby
338
- tap_on!(@post, track_as_records: true)
339
- post = Post.find(@post.id) # same record but a different object
340
- post.title #=> this call will be recorded as well
182
+ gem 'tapping_device', group: :development
341
183
  ```
342
184
 
343
- #### exclude_by_paths
344
- 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.
185
+ And then execute:
345
186
 
346
- ```ruby
347
- tap_on!(@post, exclude_by_paths: [/active_record/]).and_print(:method_name_and_location)
348
187
  ```
349
-
188
+ $ bundle
350
189
  ```
351
- _read_attribute FROM /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
352
- name FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
353
- _read_attribute FROM /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
354
- user_id FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
355
- .......
356
-
357
- # versus
358
190
 
359
- name FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
360
- user_id FROM /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
361
- to_param FROM /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
362
- ```
191
+ Or install it directly:
363
192
 
364
- #### filter_by_paths
365
-
366
- Like `exclude_by_paths`, but work in the opposite way.
367
-
368
-
369
- ### Payload of The Call
370
- All tapping methods (start with `tap_`) takes a block and yield a `Payload` object as a block argument. It responds to
371
-
372
- - `target` - the target for `tap_x` call
373
- - `receiver` - the receiver object
374
- - `method_name` - method’s name (symbol)
375
- - e.g. `:name`
376
- - `method_object` - the method object that's being called. It might be `nil` in some edge cases.
377
- - `arguments` - arguments of the method call
378
- - e.g. `{name: “Stan”, age: 25}`
379
- - `return_value` - return value of the method call
380
- - `filepath` - path to the file that performs the method call
381
- - `line_number`
382
- - `defined_class` - in which class that defines the method being called
383
- - `trace` - stack trace of the call. Default is an empty array unless `with_trace_to` option is set
384
- - `sql` - sql that generated from the call (only present in `tap_sql!` payloads)
385
- - `tp` - trace point object of this call
386
-
387
-
388
- #### Symbols for Payload Helpers
389
- - `FROM` for method call’s location
390
- - `<=` for arguments
391
- - `=>` for return value
392
- - `@` for defined class
393
-
394
- #### Payload Helpers
395
- - `method_name_and_location` - `initialize FROM /PROJECT_PATH/tapping_device/spec/payload_spec.rb:7`
396
- - `method_name_and_arguments` - `initialize <= {:name=>\"Stan\", :age=>25}`
397
- - `method_name_and_return_value` - `ten => 10`
398
- - `method_name_and_defined_class` - `initialize @ Student`
399
- - `passed_at` -
400
193
  ```
401
- Passed as 'object' in method ':initialize'
402
- 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
194
+ $ gem install tapping_device
403
195
  ```
404
196
 
405
- You can also set `passed_at(with_method_head: true)` to see the method's head.
197
+ **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**
406
198
 
407
- ```
408
- Passed as 'object' in method ':initialize'
409
- > def initialize(template_object, object_name, method_name, object, tag_value)
410
- 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
411
- ```
412
199
 
413
- - `detail_call_info`
200
+ ### Advance Usages & Options
414
201
 
202
+ #### Add Conditions With `.with`
203
+
204
+ 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.
205
+
206
+ ```ruby
207
+ # only prints calls with name matches /foo/
208
+ print_calls(object).with do |payload|
209
+ payload.method_name.to_s.match?(/foo/)
210
+ end
415
211
  ```
416
- initialize @ Student
417
- <= {:name=>"Stan", :age=>25}
418
- => 25
419
- FROM /Users/st0012/projects/tapping_device/spec/payload_spec.rb:7
420
- ```
421
212
 
422
- ### Advance Usages
213
+ #### `colorize: false`
214
+
215
+ 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.
423
216
 
424
- Tapping methods introduced above like `tap_on!` are designed for simple use cases. They're actually short for
425
217
 
426
218
  ```ruby
427
- device = TappingDevice.new { # tapping action }
428
- device.tap_on!(object)
219
+ print_calls(object, colorize: false)
429
220
  ```
430
221
 
431
- 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.
432
222
 
433
- #### Stop tapping
223
+ #### `inspect: true`
434
224
 
435
- Once you have a `TappingDevice` instance in hand, you will be able to stop the tapping by
436
- 1. Manually calling `device.stop!`
437
- 2. Setting stop condition with `device.stop_when`, like
225
+ 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:
438
226
 
439
- ```ruby
440
- device.stop_when do |payload|
441
- device.calls.count >= 10 # stop after gathering 10 calls’ data
442
- end
227
+ ``` ruby
228
+ post.to_s #=> #<Post:0x00007f89a55201d0>
229
+ 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>
443
230
  ```
444
231
 
445
- #### Device states & Managing Devices
446
232
 
447
- Each `TappingDevice` instance can have 3 states:
233
+ ### Lower-Level Helpers
234
+ `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)
448
235
 
449
- - `Initial` - means the instance is initialized but hasn't tapped on anything.
450
- - `Enabled` - means the instance is tapping on something (has called `tap_*` methods).
451
- - `Disabled` - means the instance has been disabled. It will no longer receive any call info.
452
236
 
453
- 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:
237
+ ### Related Blog Posts
238
+ - [Optimize Your Debugging Process With Object-Oriented Tracing and tapping_device](http://bit.ly/object-oriented-tracing)
239
+ - [Debug Rails issues effectively with tapping_device](https://dev.to/st0012/debug-rails-issues-effectively-with-tappingdevice-c7c)
240
+ - [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)
454
241
 
455
- - `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.
456
- - `TappingDevice.stop_all!` - Stops all registered devices and remove them from the `devices` list.
457
- - `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.
458
- - `TappingDevice.reset!` - Cancels `suspend_new` (if called) and stops/removes all created devices. Useful to reset the environment between test cases.
459
242
 
460
243
  ## Development
461
-
462
244
  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.
463
245
 
464
246
  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).
@@ -474,3 +256,4 @@ The gem is available as open-source under the terms of the [MIT License](https:/
474
256
  ## Code of Conduct
475
257
 
476
258
  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).
259
+