tapping_device 0.5.0 → 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 +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
|