tapping_device 0.4.3 → 0.4.4
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 +2 -0
- data/Gemfile.lock +7 -7
- data/README.md +51 -20
- data/lib/tapping_device.rb +66 -60
- data/lib/tapping_device/manageable.rb +37 -0
- data/lib/tapping_device/payload.rb +23 -2
- data/lib/tapping_device/sql_tapping_methods.rb +1 -0
- data/lib/tapping_device/trackable.rb +4 -14
- data/lib/tapping_device/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b224ae2b5f110b0b4f6cfce578eaf6d4170d646e8141aa087e0abbd7780938d1
|
4
|
+
data.tar.gz: 836334b532fad8983c63070ab322f88cce8578a61ff79b57fe76bd2f65f141a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c78d7c9adad4b705e8a8ec65316fe182764090cbfdfd3f0ab8a4931675f8527317031a1e0e1ac8726d613c87e777a9895b668c3376dbcc61ad9fc59501df34e
|
7
|
+
data.tar.gz: 0a6388a5c2096ce392d08951fd89f63c0f9967bbe21e99b2462893d75f454552288e49b231c2692b056b09567751c38756ff52bafd53fc7c09615534cee9c8a9
|
data/.github/workflows/ruby.yml
CHANGED
@@ -19,6 +19,8 @@ jobs:
|
|
19
19
|
ruby-version: ${{ matrix.ruby_version }}
|
20
20
|
- name: Install sqlite
|
21
21
|
run: |
|
22
|
+
# See https://github.community/t5/GitHub-Actions/ubuntu-latest-Apt-repository-list-issues/td-p/41122/page/2
|
23
|
+
sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list
|
22
24
|
sudo apt-get update
|
23
25
|
sudo apt-get install libsqlite3-dev
|
24
26
|
|
data/Gemfile.lock
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tapping_device (0.4.
|
4
|
+
tapping_device (0.4.4)
|
5
5
|
activerecord (>= 5.2)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activemodel (6.0.
|
11
|
-
activesupport (= 6.0.
|
12
|
-
activerecord (6.0.
|
13
|
-
activemodel (= 6.0.
|
14
|
-
activesupport (= 6.0.
|
15
|
-
activesupport (6.0.
|
10
|
+
activemodel (6.0.2)
|
11
|
+
activesupport (= 6.0.2)
|
12
|
+
activerecord (6.0.2)
|
13
|
+
activemodel (= 6.0.2)
|
14
|
+
activesupport (= 6.0.2)
|
15
|
+
activesupport (6.0.2)
|
16
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
17
|
i18n (>= 0.7, < 2)
|
18
18
|
minitest (~> 5.1)
|
data/README.md
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
![](https://github.com/st0012/tapping_device/workflows/Ruby/badge.svg)
|
4
4
|
|
5
|
-
**Please use 0.3.0+ versions, older versions have serious performance issues**
|
6
5
|
|
7
6
|
`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. Here are some sample usages:
|
8
7
|
|
@@ -50,7 +49,7 @@ Method: :amends_order, line: /MY_PROJECT/app/models/order.rb:432
|
|
50
49
|
```
|
51
50
|
|
52
51
|
|
53
|
-
### Track calls that generates sql queries!
|
52
|
+
### Track calls that generates sql queries!
|
54
53
|
|
55
54
|
`tap_sql!` method helps you track which method calls generate sql queries. This is particularly helpful when tracking calls created from a reused `ActiveRecord::Relation` object.
|
56
55
|
|
@@ -120,27 +119,13 @@ In order to use `tapping_device`, you need to include `TappingDevice::Trackable`
|
|
120
119
|
- `tap_sql!(activerecord_relation_or_model)` - tracks sql queries generated from the target
|
121
120
|
|
122
121
|
### Payload of the call
|
123
|
-
All tapping methods (start with `tap_`) takes a block and yield a `Payload` object as block argument.
|
124
|
-
|
125
|
-
```ruby
|
126
|
-
{
|
127
|
-
:receiver=>#<Student:0x00007fabed02aeb8 @name="Stan", @age=18, @tapping_device=[#<TracePoint:return `age'@/PROJECT_PATH/tapping_device/spec/trackable_spec.rb:17>]>,
|
128
|
-
:method_name=>:age,
|
129
|
-
:arguments=>{},
|
130
|
-
:return_value=>18,
|
131
|
-
:filepath=>"/PROJECT_PATH/tapping_device/spec/trackable_spec.rb",
|
132
|
-
:line_number=>"171",
|
133
|
-
:defined_class=>Student,
|
134
|
-
:trace=>[],
|
135
|
-
:tp=>#<TracePoint:return `age'@/PROJECT_PATH/tapping_device/spec/trackable_spec.rb:17>
|
136
|
-
}
|
137
|
-
```
|
138
|
-
|
139
|
-
The hash contains
|
122
|
+
All tapping methods (start with `tap_`) takes a block and yield a `Payload` object as block argument. It responds to
|
140
123
|
|
124
|
+
- `target` - the target for `tap_x` call
|
141
125
|
- `receiver` - the receiver object
|
142
126
|
- `method_name` - method’s name (symbol)
|
143
127
|
- e.g. `:name`
|
128
|
+
- `method_object` - the method object that’s being called. It might be `nil` in some edge cases.
|
144
129
|
- `arguments` - arguments of the method call
|
145
130
|
- e.g. `{name: “Stan”, age: 25}`
|
146
131
|
- `return_value` - return value of the method call
|
@@ -153,6 +138,19 @@ The hash contains
|
|
153
138
|
#### Some useful helpers
|
154
139
|
- `method_name_and_location` - `"Method: :initialize, line: /PROJECT_PATH/tapping_device/spec/payload_spec.rb:7"`
|
155
140
|
- `method_name_and_arguments` - `"Method: :initialize, argments: {:name=>\"Stan\", :age=>25}"`
|
141
|
+
- `passed_at` -
|
142
|
+
```
|
143
|
+
Passed as 'object' in method ':initialize'
|
144
|
+
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
|
145
|
+
```
|
146
|
+
|
147
|
+
You can also set `passed_at(with_method_head: true)` to see the method’s head
|
148
|
+
|
149
|
+
```
|
150
|
+
Passed as 'object' in method ':initialize'
|
151
|
+
> def initialize(template_object, object_name, method_name, object, tag_value)
|
152
|
+
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
|
153
|
+
```
|
156
154
|
|
157
155
|
|
158
156
|
### Options
|
@@ -221,6 +219,39 @@ Method: :user_id, line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
|
|
221
219
|
Method: :to_param, line: /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
|
222
220
|
```
|
223
221
|
|
222
|
+
### `tap_passed!`
|
223
|
+
|
224
|
+
This is particularly useful when debugging libraries. It saves your time from jumping between files and check which path the object will go.
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
class PostsController < ApplicationController
|
228
|
+
include TappingDevice::Trackable
|
229
|
+
# GET /posts/new
|
230
|
+
def new
|
231
|
+
@post = Post.new
|
232
|
+
|
233
|
+
tap_passed!(@post) do |payload|
|
234
|
+
puts(payload.passed_at(with_method_head: true))
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
```
|
241
|
+
Passed as 'record' in method ':polymorphic_mapping'
|
242
|
+
> def polymorphic_mapping(record)
|
243
|
+
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
|
244
|
+
Passed as 'klass' in method ':get_method_for_class'
|
245
|
+
> def get_method_for_class(klass)
|
246
|
+
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
|
247
|
+
Passed as 'record' in method ':handle_model'
|
248
|
+
> def handle_model(record)
|
249
|
+
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
|
250
|
+
Passed as 'record_or_hash_or_array' in method ':polymorphic_method'
|
251
|
+
> def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options)
|
252
|
+
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
|
253
|
+
```
|
254
|
+
|
224
255
|
### `tap_assoc!`
|
225
256
|
|
226
257
|
```ruby
|
@@ -237,7 +268,7 @@ Method: :amending_orders, line: /MY_PROJECT/app/models/order.rb:385
|
|
237
268
|
Method: :amends_order, line: /MY_PROJECT/app/models/order.rb:432
|
238
269
|
```
|
239
270
|
|
240
|
-
### `tap_sql!`
|
271
|
+
### `tap_sql!`
|
241
272
|
|
242
273
|
```ruby
|
243
274
|
class PostsController < ApplicationController
|
data/lib/tapping_device.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "active_record"
|
2
2
|
require "tapping_device/version"
|
3
|
+
require "tapping_device/manageable"
|
3
4
|
require "tapping_device/payload"
|
4
5
|
require "tapping_device/trackable"
|
5
6
|
require "tapping_device/exceptions"
|
@@ -10,44 +11,13 @@ class TappingDevice
|
|
10
11
|
CALLER_START_POINT = 3
|
11
12
|
C_CALLER_START_POINT = 2
|
12
13
|
|
13
|
-
attr_reader :options, :calls, :trace_point
|
14
|
+
attr_reader :options, :calls, :trace_point, :target
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
@devices = []
|
17
|
+
@suspend_new = false
|
17
18
|
|
18
19
|
include SqlTappingMethods
|
19
|
-
|
20
|
-
def self.suspend_new
|
21
|
-
@@suspend_new
|
22
|
-
end
|
23
|
-
|
24
|
-
# list all registered devices
|
25
|
-
def self.devices
|
26
|
-
@@devices
|
27
|
-
end
|
28
|
-
|
29
|
-
# disable given device and remove it from registered list
|
30
|
-
def self.delete_device(device)
|
31
|
-
device.trace_point&.disable
|
32
|
-
@@devices -= [device]
|
33
|
-
end
|
34
|
-
|
35
|
-
# stops all registered devices and remove them from registered list
|
36
|
-
def self.stop_all!
|
37
|
-
@@devices.each(&:stop!)
|
38
|
-
end
|
39
|
-
|
40
|
-
# suspend enabling new trace points
|
41
|
-
# user can still create new Device instances, but they won't be functional
|
42
|
-
def self.suspend_new!
|
43
|
-
@@suspend_new = true
|
44
|
-
end
|
45
|
-
|
46
|
-
# reset everything to clean state and disable all devices
|
47
|
-
def self.reset!
|
48
|
-
@@suspend_new = false
|
49
|
-
stop_all!
|
50
|
-
end
|
20
|
+
extend Manageable
|
51
21
|
|
52
22
|
def initialize(options = {}, &block)
|
53
23
|
@block = block
|
@@ -66,6 +36,10 @@ class TappingDevice
|
|
66
36
|
track(object, condition: :tap_on?)
|
67
37
|
end
|
68
38
|
|
39
|
+
def tap_passed!(object)
|
40
|
+
track(object, condition: :tap_passed?)
|
41
|
+
end
|
42
|
+
|
69
43
|
def tap_assoc!(record)
|
70
44
|
raise "argument should be an instance of ActiveRecord::Base" unless record.is_a?(ActiveRecord::Base)
|
71
45
|
track(record, condition: :tap_associations?)
|
@@ -87,6 +61,7 @@ class TappingDevice
|
|
87
61
|
def create_child_device
|
88
62
|
new_device = self.class.new(@options.merge(root_device: root_device), &@block)
|
89
63
|
new_device.stop_when(&@stop_when)
|
64
|
+
new_device.instance_variable_set(:@target, @target)
|
90
65
|
self.descendants << new_device
|
91
66
|
new_device
|
92
67
|
end
|
@@ -99,26 +74,12 @@ class TappingDevice
|
|
99
74
|
options[:descendants]
|
100
75
|
end
|
101
76
|
|
102
|
-
def record_call!(payload)
|
103
|
-
return if @disabled
|
104
|
-
|
105
|
-
if @block
|
106
|
-
root_device.calls << @block.call(payload)
|
107
|
-
else
|
108
|
-
root_device.calls << payload
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
77
|
private
|
113
78
|
|
114
79
|
def track(object, condition:)
|
80
|
+
@target = object
|
115
81
|
@trace_point = TracePoint.new(:return) do |tp|
|
116
|
-
|
117
|
-
receiver: tp.self,
|
118
|
-
method_name: tp.callee_id
|
119
|
-
}
|
120
|
-
|
121
|
-
if send(condition, object, validation_params)
|
82
|
+
if send(condition, object, tp)
|
122
83
|
filepath, line_number = get_call_location(tp)
|
123
84
|
|
124
85
|
next if should_be_skipped_by_paths?(filepath)
|
@@ -131,7 +92,7 @@ class TappingDevice
|
|
131
92
|
end
|
132
93
|
end
|
133
94
|
|
134
|
-
@trace_point.enable unless
|
95
|
+
@trace_point.enable unless self.class.suspend_new
|
135
96
|
|
136
97
|
self
|
137
98
|
end
|
@@ -155,8 +116,10 @@ class TappingDevice
|
|
155
116
|
tp.binding.local_variables.each { |name| arguments[name] = tp.binding.local_variable_get(name) }
|
156
117
|
|
157
118
|
Payload.init({
|
119
|
+
target: @target,
|
158
120
|
receiver: tp.self,
|
159
121
|
method_name: tp.callee_id,
|
122
|
+
method_object: get_method_object_from(tp.self, tp.callee_id),
|
160
123
|
arguments: arguments,
|
161
124
|
return_value: (tp.return_value rescue nil),
|
162
125
|
filepath: filepath,
|
@@ -167,9 +130,9 @@ class TappingDevice
|
|
167
130
|
})
|
168
131
|
end
|
169
132
|
|
170
|
-
def tap_init?(klass,
|
171
|
-
receiver =
|
172
|
-
method_name =
|
133
|
+
def tap_init?(klass, tp)
|
134
|
+
receiver = tp.self
|
135
|
+
method_name = tp.callee_id
|
173
136
|
|
174
137
|
if klass.ancestors.include?(ActiveRecord::Base)
|
175
138
|
method_name == :new && receiver.ancestors.include?(klass)
|
@@ -178,16 +141,49 @@ class TappingDevice
|
|
178
141
|
end
|
179
142
|
end
|
180
143
|
|
181
|
-
def tap_on?(object,
|
182
|
-
|
144
|
+
def tap_on?(object, tp)
|
145
|
+
is_from_target?(object, tp)
|
183
146
|
end
|
184
147
|
|
185
|
-
def tap_associations?(object,
|
186
|
-
return false unless tap_on?(object,
|
148
|
+
def tap_associations?(object, tp)
|
149
|
+
return false unless tap_on?(object, tp)
|
187
150
|
|
188
151
|
model_class = object.class
|
189
152
|
associations = model_class.reflections
|
190
|
-
associations.keys.include?(
|
153
|
+
associations.keys.include?(tp.callee_id.to_s)
|
154
|
+
end
|
155
|
+
|
156
|
+
def tap_passed?(object, tp)
|
157
|
+
# we don't care about calls from the device instance
|
158
|
+
return false if is_from_target?(self, tp)
|
159
|
+
|
160
|
+
method_object = get_method_object_from(tp.self, tp.callee_id)
|
161
|
+
# if a no-arugment method is called, tp.binding.local_variables will be those local variables in the same scope
|
162
|
+
# so we need to make sure the method takes arguments, then we can be sure that the locals are arguments
|
163
|
+
return false unless method_object && method_object.arity.to_i > 0
|
164
|
+
|
165
|
+
argument_values = tp.binding.local_variables.map { |name| tp.binding.local_variable_get(name) }
|
166
|
+
|
167
|
+
argument_values.any? do |value|
|
168
|
+
# during comparison, Ruby might perform data type conversion like calling `to_sym` on the value
|
169
|
+
# but not every value supports every conversion methods
|
170
|
+
begin
|
171
|
+
object == value
|
172
|
+
rescue NoMethodError
|
173
|
+
false
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def get_method_object_from(target, method_name)
|
179
|
+
target.method(method_name)
|
180
|
+
rescue ArgumentError
|
181
|
+
method_method = Object.method(:method).unbind
|
182
|
+
method_method.bind(target).call(method_name)
|
183
|
+
rescue NameError
|
184
|
+
# if any part of the program uses Refinement to extend its methods
|
185
|
+
# we might still get NoMethodError when trying to get that method outside the scope
|
186
|
+
nil
|
191
187
|
end
|
192
188
|
|
193
189
|
def process_options(options)
|
@@ -209,4 +205,14 @@ class TappingDevice
|
|
209
205
|
def is_from_target?(object, tp)
|
210
206
|
object.__id__ == tp.self.__id__
|
211
207
|
end
|
208
|
+
|
209
|
+
def record_call!(payload)
|
210
|
+
return if @disabled
|
211
|
+
|
212
|
+
if @block
|
213
|
+
root_device.calls << @block.call(payload)
|
214
|
+
else
|
215
|
+
root_device.calls << payload
|
216
|
+
end
|
217
|
+
end
|
212
218
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class TappingDevice
|
2
|
+
module Manageable
|
3
|
+
|
4
|
+
def suspend_new
|
5
|
+
@suspend_new
|
6
|
+
end
|
7
|
+
|
8
|
+
# list all registered devices
|
9
|
+
def devices
|
10
|
+
@devices
|
11
|
+
end
|
12
|
+
|
13
|
+
# disable given device and remove it from registered list
|
14
|
+
def delete_device(device)
|
15
|
+
device.trace_point&.disable
|
16
|
+
@devices -= [device]
|
17
|
+
end
|
18
|
+
|
19
|
+
# stops all registered devices and remove them from registered list
|
20
|
+
def stop_all!
|
21
|
+
@devices.each(&:stop!)
|
22
|
+
end
|
23
|
+
|
24
|
+
# suspend enabling new trace points
|
25
|
+
# user can still create new Device instances, but they won't be functional
|
26
|
+
def suspend_new!
|
27
|
+
@suspend_new = true
|
28
|
+
end
|
29
|
+
|
30
|
+
# reset everything to clean state and disable all devices
|
31
|
+
def reset!
|
32
|
+
@suspend_new = false
|
33
|
+
stop_all!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -1,6 +1,9 @@
|
|
1
1
|
class TappingDevice
|
2
2
|
class Payload < Hash
|
3
|
-
ATTRS = [
|
3
|
+
ATTRS = [
|
4
|
+
:target, :receiver, :method_name, :method_object, :arguments, :return_value, :filepath, :line_number,
|
5
|
+
:defined_class, :trace, :tp, :sql
|
6
|
+
]
|
4
7
|
|
5
8
|
ATTRS.each do |attr|
|
6
9
|
define_method attr do
|
@@ -16,8 +19,26 @@ class TappingDevice
|
|
16
19
|
h
|
17
20
|
end
|
18
21
|
|
22
|
+
def passed_at(with_method_head: false)
|
23
|
+
arg_name = arguments.keys.detect { |k| arguments[k] == target }
|
24
|
+
return unless arg_name
|
25
|
+
msg = "Passed as '#{arg_name}' in method ':#{method_name}'"
|
26
|
+
msg += "\n > #{method_head.strip}" if with_method_head
|
27
|
+
msg += "\n at #{location}"
|
28
|
+
msg
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_head
|
32
|
+
source_file, source_line = method_object.source_location
|
33
|
+
IO.readlines(source_file)[source_line-1]
|
34
|
+
end
|
35
|
+
|
36
|
+
def location
|
37
|
+
"#{filepath}:#{line_number}"
|
38
|
+
end
|
39
|
+
|
19
40
|
def method_name_and_location
|
20
|
-
"Method: :#{method_name}, line: #{
|
41
|
+
"Method: :#{method_name}, line: #{location}"
|
21
42
|
end
|
22
43
|
|
23
44
|
def method_name_and_arguments
|
@@ -13,6 +13,7 @@ class TappingDevice
|
|
13
13
|
|
14
14
|
def tap_sql!(object)
|
15
15
|
@call_stack = []
|
16
|
+
@target ||= object
|
16
17
|
@trace_point = with_trace_point_on_target(object, event: [:call, :c_call]) do |start_tp|
|
17
18
|
########## Check if the call is worth recording ##########
|
18
19
|
filepath, line_number = get_call_location(start_tp, padding: 1) # we need extra padding because of `with_trace_point_on_target`
|
@@ -1,19 +1,9 @@
|
|
1
1
|
class TappingDevice
|
2
2
|
module Trackable
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
def tap_assoc!(record, options = {}, &block)
|
8
|
-
new_device(options, &block).tap_assoc!(record)
|
9
|
-
end
|
10
|
-
|
11
|
-
def tap_on!(object, options = {}, &block)
|
12
|
-
new_device(options, &block).tap_on!(object)
|
13
|
-
end
|
14
|
-
|
15
|
-
def tap_sql!(object, options = {}, &block)
|
16
|
-
new_device(options, &block).tap_sql!(object)
|
3
|
+
[:tap_on!, :tap_init!, :tap_assoc!, :tap_sql!, :tap_passed!].each do |method|
|
4
|
+
define_method method do |object, options = {}, &block|
|
5
|
+
new_device(options, &block).send(method, object)
|
6
|
+
end
|
17
7
|
end
|
18
8
|
|
19
9
|
def new_device(options, &block)
|
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.4.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- st0012
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-12-
|
11
|
+
date: 2019-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -130,6 +130,7 @@ files:
|
|
130
130
|
- bin/setup
|
131
131
|
- lib/tapping_device.rb
|
132
132
|
- lib/tapping_device/exceptions.rb
|
133
|
+
- lib/tapping_device/manageable.rb
|
133
134
|
- lib/tapping_device/payload.rb
|
134
135
|
- lib/tapping_device/sql_tapping_methods.rb
|
135
136
|
- lib/tapping_device/trackable.rb
|