tapping_device 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +11 -4
- data/Gemfile.lock +2 -2
- data/README.md +1 -1
- data/lib/tapping_device.rb +7 -2
- data/lib/tapping_device/trackable.rb +16 -2
- data/lib/tapping_device/version.rb +1 -1
- metadata +2 -3
- data/lib/tapping_device/sql_tapping_methods.rb +0 -89
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b44a9cf6d9eb0e17b965c87b38f5c431058c283c2a6b79dcce1d2b88aafcc5e
|
4
|
+
data.tar.gz: 6afad055a9606958c47534cfa68394bc859c465300ac2c3f9959335edc0b7f4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e36715dc9d885af5b143ace2b5c3af6ff782e49d371e4b24787ac475f58d140abbad1a9730769722a59c2e06240a3a72930d9588f488595eaa6dc75973a17a31
|
7
|
+
data.tar.gz: e3bd4a46af607281dc82e6bb4e3f57ad54f600d99a25892d7914c4e5577dcf8a3dc212d60449e9ced5b1789680eead0f79f3758ef6b06c25b1b7647f52e2282e
|
data/.github/workflows/ruby.yml
CHANGED
@@ -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 }}
|
37
|
-
|
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
|
-
|
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
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tapping_device (0.5.
|
4
|
+
tapping_device (0.5.1)
|
5
5
|
activerecord (>= 5.2)
|
6
6
|
|
7
7
|
GEM
|
@@ -23,7 +23,7 @@ GEM
|
|
23
23
|
database_cleaner (1.7.0)
|
24
24
|
diff-lcs (1.3)
|
25
25
|
docile (1.3.2)
|
26
|
-
i18n (1.8.
|
26
|
+
i18n (1.8.3)
|
27
27
|
concurrent-ruby (~> 1.0)
|
28
28
|
json (2.3.0)
|
29
29
|
method_source (0.9.2)
|
data/README.md
CHANGED
@@ -21,7 +21,7 @@ Still sounds vague? Let's see some examples:
|
|
21
21
|
|
22
22
|
### `print_calls` To Track Method Calls
|
23
23
|
|
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 `
|
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:
|
25
25
|
|
26
26
|
```ruby
|
27
27
|
def create
|
data/lib/tapping_device.rb
CHANGED
@@ -5,7 +5,6 @@ require "tapping_device/payload"
|
|
5
5
|
require "tapping_device/output_payload"
|
6
6
|
require "tapping_device/trackable"
|
7
7
|
require "tapping_device/exceptions"
|
8
|
-
require "tapping_device/sql_tapping_methods"
|
9
8
|
|
10
9
|
class TappingDevice
|
11
10
|
|
@@ -17,7 +16,6 @@ class TappingDevice
|
|
17
16
|
@devices = []
|
18
17
|
@suspend_new = false
|
19
18
|
|
20
|
-
include SqlTappingMethods
|
21
19
|
extend Manageable
|
22
20
|
|
23
21
|
def initialize(options = {}, &block)
|
@@ -111,6 +109,13 @@ class TappingDevice
|
|
111
109
|
|
112
110
|
next unless with_condition_satisfied?(payload)
|
113
111
|
|
112
|
+
# skip TappingDevice related calls
|
113
|
+
if Module.respond_to?(:module_parents)
|
114
|
+
next if payload.defined_class.module_parents.include?(TappingDevice)
|
115
|
+
else
|
116
|
+
next if payload.defined_class.parents.include?(TappingDevice)
|
117
|
+
end
|
118
|
+
|
114
119
|
record_call!(payload)
|
115
120
|
|
116
121
|
stop_if_condition_fulfilled(payload)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class TappingDevice
|
2
2
|
module Trackable
|
3
|
-
[:tap_on!, :tap_init!, :tap_assoc!, :
|
3
|
+
[:tap_on!, :tap_init!, :tap_assoc!, :tap_passed!].each do |method|
|
4
4
|
define_method method do |object, options = {}, &block|
|
5
5
|
new_device(options, &block).send(method, object)
|
6
6
|
end
|
@@ -17,7 +17,7 @@ class TappingDevice
|
|
17
17
|
device_2 = tap_passed!(target, options).and_print do |output_payload|
|
18
18
|
output_payload.passed_at(inspect: inspect, colorize: colorize)
|
19
19
|
end
|
20
|
-
[device_1, device_2]
|
20
|
+
CollectionProxy.new([device_1, device_2])
|
21
21
|
end
|
22
22
|
|
23
23
|
def print_calls(target, options = {})
|
@@ -32,6 +32,20 @@ class TappingDevice
|
|
32
32
|
def new_device(options, &block)
|
33
33
|
TappingDevice.new(options, &block)
|
34
34
|
end
|
35
|
+
|
36
|
+
class CollectionProxy
|
37
|
+
def initialize(devices)
|
38
|
+
@devices = devices
|
39
|
+
end
|
40
|
+
|
41
|
+
[:stop!, :stop_when, :with].each do |method|
|
42
|
+
define_method method do |&block|
|
43
|
+
@devices.each do |device|
|
44
|
+
device.send(method, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
35
49
|
end
|
36
50
|
end
|
37
51
|
|
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.5.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- st0012
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -153,7 +153,6 @@ files:
|
|
153
153
|
- lib/tapping_device/manageable.rb
|
154
154
|
- lib/tapping_device/output_payload.rb
|
155
155
|
- lib/tapping_device/payload.rb
|
156
|
-
- lib/tapping_device/sql_tapping_methods.rb
|
157
156
|
- lib/tapping_device/trackable.rb
|
158
157
|
- lib/tapping_device/version.rb
|
159
158
|
- tapping_device.gemspec
|
@@ -1,89 +0,0 @@
|
|
1
|
-
class TappingDevice
|
2
|
-
module SqlTappingMethods
|
3
|
-
CALL_STACK_SKIPPABLE_METHODS = [:transaction, :tap]
|
4
|
-
|
5
|
-
# SQLListener acts like an interface for us to intercept activerecord query instrumentations
|
6
|
-
# this means we only need to register one subscriber no matter how many objects we want to tap on
|
7
|
-
class SQLListener
|
8
|
-
def call(name, start, finish, message_id, values);end
|
9
|
-
end
|
10
|
-
@@sql_listener = SQLListener.new
|
11
|
-
|
12
|
-
ActiveSupport::Notifications.subscribe("sql.active_record", @@sql_listener)
|
13
|
-
|
14
|
-
def tap_sql!(object)
|
15
|
-
@call_stack = []
|
16
|
-
@target ||= object
|
17
|
-
@trace_point = with_trace_point_on_target(object, event: [:call, :c_call]) do |start_tp|
|
18
|
-
########## Check if the call is worth recording ##########
|
19
|
-
filepath, line_number = get_call_location(start_tp, padding: 1) # we need extra padding because of `with_trace_point_on_target`
|
20
|
-
method = start_tp.callee_id
|
21
|
-
next if already_recording?(method)
|
22
|
-
|
23
|
-
########## Start the recording ##########
|
24
|
-
# 1. Mark recording state by pushing method into @call_stack
|
25
|
-
# 2. Subscribe sql instrumentations generated by activerecord
|
26
|
-
# 3. Record those sqls and run device callbacks
|
27
|
-
# 4. Start waiting for current call's return callback
|
28
|
-
@call_stack.push(method)
|
29
|
-
payload = build_payload(tp: start_tp, filepath: filepath, line_number: line_number)
|
30
|
-
device = tap_on_sql_instrumentation!(payload)
|
31
|
-
|
32
|
-
with_trace_point_on_target(object, event: :return) do |return_tp|
|
33
|
-
next unless return_tp.callee_id == method
|
34
|
-
|
35
|
-
########## End recording ##########
|
36
|
-
# 1. Close itself
|
37
|
-
# 2. Stop our subscription on SQLListener
|
38
|
-
# 3. Remove current method from @call_stack
|
39
|
-
# 4. Stop the device if stop condition is fulfilled
|
40
|
-
return_tp.disable
|
41
|
-
device.stop!
|
42
|
-
@call_stack.pop
|
43
|
-
stop_if_condition_fulfilled(payload)
|
44
|
-
|
45
|
-
########## Track descendant objects ##########
|
46
|
-
# if the method creates another Relation object
|
47
|
-
if return_tp.defined_class == ActiveRecord::QueryMethods
|
48
|
-
create_child_device.tap_sql!(return_tp.return_value)
|
49
|
-
end
|
50
|
-
end.enable
|
51
|
-
end
|
52
|
-
|
53
|
-
@trace_point.enable unless self.class.suspend_new
|
54
|
-
|
55
|
-
self
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def tap_on_sql_instrumentation!(payload)
|
62
|
-
device = TappingDevice.new do |sql_listener_payload|
|
63
|
-
values = sql_listener_payload.arguments[:values]
|
64
|
-
|
65
|
-
next if should_be_skipped_by_paths?(payload.filepath) ||
|
66
|
-
["SCHEMA", "TRANSACTION"].include?(values[:name]) ||
|
67
|
-
values[:sql].match?(/SAVEPOINT/)
|
68
|
-
|
69
|
-
payload[:sql] = values[:sql]
|
70
|
-
record_call!(payload)
|
71
|
-
end
|
72
|
-
device.tap_on!(@@sql_listener)
|
73
|
-
end
|
74
|
-
|
75
|
-
# usually, AR's query methods (like `first`) will end up calling `find_by_sql`
|
76
|
-
# then to TappingDevice, both `first` and `find_by_sql` generates the sql
|
77
|
-
# but the results are duplicated, we should only consider the `first` call
|
78
|
-
def already_recording?(method)
|
79
|
-
!@call_stack.empty? || CALL_STACK_SKIPPABLE_METHODS.include?(method)
|
80
|
-
end
|
81
|
-
|
82
|
-
def with_trace_point_on_target(object, event:)
|
83
|
-
TracePoint.new(*event) do |tp|
|
84
|
-
if is_from_target?(object, tp)
|
85
|
-
yield(tp)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|