tapping_device 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16d62dfe61cf06c2f3932ed32bc3a5c20ca652c743a1a238f156ff738103357e
4
- data.tar.gz: ee964e53de1c35e0b3bc1d6be28f0fb5417d7a51bff5e9f8a16cc355297e3fcf
3
+ metadata.gz: 86f08b96d9d7d45cc1f0292a69749e5e5d853167e54a1981a26ec2b3905e31bd
4
+ data.tar.gz: 50a7e86833462d2392150182abb75d37bde739b04f582a56dc9438f26f34cbc9
5
5
  SHA512:
6
- metadata.gz: 3b6fe7b5a903f3d315c8258c1b4d7b22b24688eed0b13c7a8d505418bfc9f4e19cb8b1ef77a2494fadea1f4152130bb7ae41f401cadd37751d979169a417f6dc
7
- data.tar.gz: 2a865ef86a2161a5b932b677e0ab6a953b8db083589388c9d0e59ba40214ae607691df7ed020306023c87fb92d1514821f0cccd4826fc97764a47de19f3d61f9
6
+ metadata.gz: 3620752c8c95e09ebd81891f73ed5d30f2ff0d6b09aba90f0c34267936d7cd01e517beef6ea61aa5648124ba6d84c221ed0a42ae21a035bf12a7eb4bbdb4cf7e
7
+ data.tar.gz: 2fc5bd412fbab8e6cb33c28b12b89c7a403b4f481844bcca096838119887e3321fef37d21ad3374ac88c26b0c93efc56b59691000f8d62b067c8acf690e69075
@@ -1,6 +1,6 @@
1
1
  name: Ruby
2
2
 
3
- on: [push]
3
+ on: [push, pull_request]
4
4
 
5
5
  jobs:
6
6
  test:
data/Gemfile.lock CHANGED
@@ -2,25 +2,25 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  tapping_device (0.3.0)
5
- activerecord (~> 5.2)
5
+ activerecord (>= 5.2)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activemodel (5.2.3)
11
- activesupport (= 5.2.3)
12
- activerecord (5.2.3)
13
- activemodel (= 5.2.3)
14
- activesupport (= 5.2.3)
15
- arel (>= 9.0)
16
- activesupport (5.2.3)
10
+ activemodel (6.0.1)
11
+ activesupport (= 6.0.1)
12
+ activerecord (6.0.1)
13
+ activemodel (= 6.0.1)
14
+ activesupport (= 6.0.1)
15
+ activesupport (6.0.1)
17
16
  concurrent-ruby (~> 1.0, >= 1.0.2)
18
17
  i18n (>= 0.7, < 2)
19
18
  minitest (~> 5.1)
20
19
  tzinfo (~> 1.1)
21
- arel (9.0.0)
20
+ zeitwerk (~> 2.2)
22
21
  coderay (1.1.2)
23
22
  concurrent-ruby (1.1.5)
23
+ database_cleaner (1.7.0)
24
24
  diff-lcs (1.3)
25
25
  i18n (1.7.0)
26
26
  concurrent-ruby (~> 1.0)
@@ -43,20 +43,22 @@ GEM
43
43
  diff-lcs (>= 1.2.0, < 2.0)
44
44
  rspec-support (~> 3.8.0)
45
45
  rspec-support (3.8.2)
46
- sqlite3 (1.3.13)
46
+ sqlite3 (1.4.1)
47
47
  thread_safe (0.3.6)
48
48
  tzinfo (1.2.5)
49
49
  thread_safe (~> 0.1)
50
+ zeitwerk (2.2.1)
50
51
 
51
52
  PLATFORMS
52
53
  ruby
53
54
 
54
55
  DEPENDENCIES
55
56
  bundler (~> 2.0)
57
+ database_cleaner
56
58
  pry
57
59
  rake (~> 10.0)
58
60
  rspec (~> 3.0)
59
- sqlite3 (~> 1.3.6)
61
+ sqlite3 (>= 1.3.6)
60
62
  tapping_device!
61
63
 
62
64
  BUNDLED WITH
data/README.md CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  ![](https://github.com/st0012/tapping_device/workflows/Ruby/badge.svg)
4
4
 
5
- `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. For example, you can use it to see who calls your `Post` records
5
+ **Please use 0.3.0+ versions, older versions have serious performance issues**
6
+
7
+ `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
+
9
+ ### Track method calls
6
10
 
7
11
  ```ruby
8
12
  class PostsController < ApplicationController
@@ -25,6 +29,9 @@ Method: user_id line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
25
29
  Method: to_param line: /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
26
30
  ```
27
31
 
32
+
33
+ ### Track ActiveRecord records’ association calls
34
+
28
35
  Or you can use `tap_assoc!`. This is very useful for tracking potential n+1 query calls, here’s a sample from my work
29
36
 
30
37
  ```ruby
@@ -41,8 +48,46 @@ Assoc: amending_orders line: /MY_PROJECT/app/models/order.rb:385
41
48
  Assoc: amends_order line: /MY_PROJECT/app/models/order.rb:432
42
49
  ```
43
50
 
44
- However, depending on the size of your application, tapping any object could **harm the performance significantly**. **Don’t use this on production**
45
51
 
52
+ ### Track calls that generates sql queries! (Beta)
53
+
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.
55
+
56
+ ```ruby
57
+ class PostsController < ApplicationController
58
+ def index
59
+ # simulate current_user
60
+ @current_user = User.last
61
+ # reusable ActiveRecord::Relation
62
+ @posts = Post.all
63
+
64
+ tap_sql!(@posts) do |payload|
65
+ puts("Method: #{payload[:method_name]} generated sql: #{payload[:sql]} from #{payload[:filepath]}:#{payload[:line_number]}")
66
+ end
67
+ end
68
+ end
69
+ ```
70
+
71
+ ```erb
72
+ <h1>Posts (<%= @posts.count %>)</h1>
73
+ ......
74
+ <% @posts.each do |post| %>
75
+ ......
76
+ <% end %>
77
+ ......
78
+ <p>Posts created by you: <%= @posts.where(user: @current_user).count %></p>
79
+ ```
80
+
81
+ And the output would be
82
+
83
+ ```
84
+ Method: count generated sql: SELECT COUNT(*) FROM "posts" from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:3
85
+ Method: each generated sql: SELECT "posts".* FROM "posts" from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:16
86
+ 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
87
+ ```
88
+
89
+
90
+ However, depending on the size of your application, tapping any object could **harm the performance significantly**. **Don’t use this on production**
46
91
 
47
92
  ## Installation
48
93
 
@@ -71,6 +116,7 @@ In order to use `tapping_device`, you need to include `TappingDevice::Trackable`
71
116
  - `tap_init!(class)` - tracks a class’ instance initialization
72
117
  - `tap_on!(object)` - tracks any calls received by the object
73
118
  - `tap_assoc!(activerecord_object)` - tracks association calls on a record, like `post.comments`
119
+ - `tap_sql!(activerecord_relation_or_model)` - tracks sql queries generated from the target
74
120
 
75
121
  ### Info of the call
76
122
  All tapping methods (start with `tap_`) takes a block and yield a hash as block argument.
@@ -184,6 +230,41 @@ Assoc: amending_orders line: /MY_PROJECT/app/models/order.rb:385
184
230
  Assoc: amends_order line: /MY_PROJECT/app/models/order.rb:432
185
231
  ```
186
232
 
233
+ ### `tap_sql!` (beta)
234
+
235
+ ```ruby
236
+ class PostsController < ApplicationController
237
+ def index
238
+ # simulate current_user
239
+ @current_user = User.last
240
+ # reusable ActiveRecord::Relation
241
+ @posts = Post.all
242
+
243
+ tap_sql!(@posts) do |payload|
244
+ puts("Method: #{payload[:method_name]} generated sql: #{payload[:sql]} from #{payload[:filepath]}:#{payload[:line_number]}")
245
+ end
246
+ end
247
+ end
248
+ ```
249
+
250
+ ```erb
251
+ <h1>Posts (<%= @posts.count %>)</h1>
252
+ ......
253
+ <% @posts.each do |post| %>
254
+ ......
255
+ <% end %>
256
+ ......
257
+ <p>Posts created by you: <%= @posts.where(user: @current_user).count %></p>
258
+ ```
259
+
260
+ ```
261
+ Method: count generated sql: SELECT COUNT(*) FROM "posts" from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:3
262
+ Method: each generated sql: SELECT "posts".* FROM "posts" from /PROJECT_PATH/rails-6-sample/app/views/posts/index.html.erb:16
263
+ 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
264
+ ```
265
+
266
+
267
+
187
268
  ### Advance Usages
188
269
 
189
270
  Tapping methods introduced above like `tap_on!` are designed for simple use cases. They’re actually short for
@@ -0,0 +1,10 @@
1
+ class TappingDevice
2
+ class SqlListenser
3
+ attr_reader :method, :payload, :device
4
+ def initialize(method, payload, device)
5
+ @method = method
6
+ @payload = payload
7
+ @device = device
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,67 @@
1
+ require "tapping_device/sql_listener"
2
+
3
+ class TappingDevice
4
+ module SqlTappingMethods
5
+ @@sql_listeners = []
6
+
7
+ ActiveSupport::Notifications.subscribe('sql.active_record') do |_1, _2, _3, _4, payload|
8
+ if !["SCHEMA", "TRANSACTION"].include? payload[:name]
9
+ @@sql_listeners.each do |listener|
10
+ listener.payload[:sql] = payload[:sql]
11
+ listener.payload[:binds] = payload[:binds]
12
+ listener.device.record_call!(listener.payload)
13
+ end
14
+ end
15
+ end
16
+
17
+ def tap_sql!(object)
18
+ @trace_point = TracePoint.new(:call, :b_call, :c_call) do |start_tp|
19
+ method = start_tp.callee_id
20
+
21
+ if is_from_target?(object, start_tp)
22
+ filepath, line_number = get_call_location(start_tp)
23
+
24
+ next if should_be_skip_by_paths?(filepath)
25
+
26
+ yield_parameters = build_yield_parameters(tp: start_tp, filepath: filepath, line_number: line_number)
27
+
28
+ # usually, AR's query methods (like `first`) will end up calling `find_by_sql`
29
+ # then to TappingDevice, both `first` and `find_by_sql` generates the sql
30
+ # but the results are duplicated, we should only consider the `first` call
31
+ # so @in_call is used to determine if we're already in a middle of a call
32
+ # it's not an optimal solution and should be updated
33
+ next if @in_call
34
+
35
+ @in_call = true
36
+
37
+ sql_listener = SqlListenser.new(method, yield_parameters, self)
38
+
39
+ @@sql_listeners << sql_listener
40
+
41
+ # return of the method call
42
+ TracePoint.trace(:return) do |return_tp|
43
+ if is_from_target?(object, return_tp)
44
+ # if it's a query method, end the sql tapping
45
+ if return_tp.callee_id == method
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
+
51
+ @@sql_listeners.delete(sql_listener)
52
+ return_tp.disable
53
+ @in_call = false
54
+
55
+ stop_if_condition_fulfilled(yield_parameters)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ @trace_point.enable unless self.class.suspend_new
63
+
64
+ self
65
+ end
66
+ end
67
+ end
@@ -1,15 +1,23 @@
1
1
  class TappingDevice
2
2
  module Trackable
3
3
  def tap_init!(klass, options = {}, &block)
4
- TappingDevice.new(options, &block).tap_init!(klass)
4
+ new_device(options, &block).tap_init!(klass)
5
5
  end
6
6
 
7
7
  def tap_assoc!(record, options = {}, &block)
8
- TappingDevice.new(options, &block).tap_assoc!(record)
8
+ new_device(options, &block).tap_assoc!(record)
9
9
  end
10
10
 
11
11
  def tap_on!(object, options = {}, &block)
12
- TappingDevice.new(options, &block).tap_on!(object)
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)
17
+ end
18
+
19
+ def new_device(options, &block)
20
+ TappingDevice.new(options, &block)
13
21
  end
14
22
  end
15
23
  end
@@ -1,3 +1,3 @@
1
1
  class TappingDevice
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -2,15 +2,24 @@ require "active_record"
2
2
  require "tapping_device/version"
3
3
  require "tapping_device/trackable"
4
4
  require "tapping_device/exceptions"
5
+ require "tapping_device/sql_tapping_methods"
5
6
 
6
7
  class TappingDevice
7
- CALLER_START_POINT = 2
8
+
9
+ CALLER_START_POINT = 3
10
+ C_CALLER_START_POINT = 2
8
11
 
9
12
  attr_reader :options, :calls, :trace_point
10
13
 
11
14
  @@devices = []
12
15
  @@suspend_new = false
13
16
 
17
+ include SqlTappingMethods
18
+
19
+ def self.suspend_new
20
+ @@suspend_new
21
+ end
22
+
14
23
  # list all registered devices
15
24
  def self.devices
16
25
  @@devices
@@ -41,23 +50,24 @@ class TappingDevice
41
50
 
42
51
  def initialize(options = {}, &block)
43
52
  @block = block
44
- @options = options
53
+ @options = process_options(options)
45
54
  @calls = []
55
+ @disabled = false
46
56
  self.class.devices << self
47
57
  end
48
58
 
49
59
  def tap_init!(klass)
50
60
  raise "argument should be a class, got #{klass}" unless klass.is_a?(Class)
51
- track(klass, condition: :tap_init?, block: @block, **@options)
61
+ track(klass, condition: :tap_init?)
52
62
  end
53
63
 
54
64
  def tap_on!(object)
55
- track(object, condition: :tap_on?, block: @block, **@options)
65
+ track(object, condition: :tap_on?)
56
66
  end
57
67
 
58
68
  def tap_assoc!(record)
59
69
  raise "argument should be an instance of ActiveRecord::Base" unless record.is_a?(ActiveRecord::Base)
60
- track(record, condition: :tap_associations?, block: @block, **@options)
70
+ track(record, condition: :tap_associations?)
61
71
  end
62
72
 
63
73
  def set_block(&block)
@@ -65,6 +75,7 @@ class TappingDevice
65
75
  end
66
76
 
67
77
  def stop!
78
+ @disabled = true
68
79
  self.class.delete_device(self)
69
80
  end
70
81
 
@@ -72,9 +83,34 @@ class TappingDevice
72
83
  @stop_when = block
73
84
  end
74
85
 
86
+ def create_child_device
87
+ new_device = self.class.new(@options.merge(root_device: root_device), &@block)
88
+ new_device.stop_when(&@stop_when)
89
+ self.descendants << new_device
90
+ new_device
91
+ end
92
+
93
+ def root_device
94
+ options[:root_device]
95
+ end
96
+
97
+ def descendants
98
+ options[:descendants]
99
+ end
100
+
101
+ def record_call!(yield_parameters)
102
+ return if @disabled
103
+
104
+ if @block
105
+ root_device.calls << @block.call(yield_parameters)
106
+ else
107
+ root_device.calls << yield_parameters
108
+ end
109
+ end
110
+
75
111
  private
76
112
 
77
- def track(object, condition:, block:, with_trace_to: nil, exclude_by_paths: [], filter_by_paths: nil)
113
+ def track(object, condition:)
78
114
  @trace_point = TracePoint.new(:return) do |tp|
79
115
  validation_params = {
80
116
  receiver: tp.self,
@@ -82,39 +118,16 @@ class TappingDevice
82
118
  }
83
119
 
84
120
  if send(condition, object, validation_params)
121
+ filepath, line_number = get_call_location(tp)
85
122
 
86
- filepath, line_number = caller(CALLER_START_POINT).first.split(":")[0..1]
87
-
88
- # this needs to be placed upfront so we can exclude noise before doing more work
89
- next if exclude_by_paths.any? { |pattern| pattern.match?(filepath) }
90
-
91
- if filter_by_paths
92
- next unless filter_by_paths.any? { |pattern| pattern.match?(filepath) }
93
- end
123
+ next if should_be_skip_by_paths?(filepath)
94
124
 
95
- arguments = tp.binding.local_variables.map { |n| [n, tp.binding.local_variable_get(n)] }
125
+ yield_parameters = build_yield_parameters(tp: tp, filepath: filepath, line_number: line_number)
96
126
 
97
- yield_parameters = {
98
- receiver: tp.self,
99
- method_name: tp.callee_id,
100
- arguments: arguments,
101
- return_value: (tp.return_value rescue nil),
102
- filepath: filepath,
103
- line_number: line_number,
104
- defined_class: tp.defined_class,
105
- trace: [],
106
- tp: tp
107
- }
127
+ record_call!(yield_parameters)
108
128
 
109
- yield_parameters[:trace] = caller[CALLER_START_POINT..(CALLER_START_POINT + with_trace_to)] if with_trace_to
110
- if @block
111
- @calls << block.call(yield_parameters)
112
- else
113
- @calls << yield_parameters
114
- end
129
+ stop_if_condition_fulfilled(yield_parameters)
115
130
  end
116
-
117
- stop! if @stop_when&.call(yield_parameters)
118
131
  end
119
132
 
120
133
  @trace_point.enable unless @@suspend_new
@@ -122,7 +135,35 @@ class TappingDevice
122
135
  self
123
136
  end
124
137
 
125
- private
138
+ def get_call_location(tp)
139
+ if tp.event == :c_call
140
+ caller(C_CALLER_START_POINT)
141
+ else
142
+ caller(CALLER_START_POINT)
143
+ end.first.split(":")[0..1]
144
+ end
145
+
146
+ # this needs to be placed upfront so we can exclude noise before doing more work
147
+ def should_be_skip_by_paths?(filepath)
148
+ options[:exclude_by_paths].any? { |pattern| pattern.match?(filepath) } ||
149
+ (options[:filter_by_paths].present? && !options[:filter_by_paths].any? { |pattern| pattern.match?(filepath) })
150
+ end
151
+
152
+ def build_yield_parameters(tp:, filepath:, line_number:)
153
+ arguments = tp.binding.local_variables.map { |n| [n, tp.binding.local_variable_get(n)] }
154
+
155
+ {
156
+ receiver: tp.self,
157
+ method_name: tp.callee_id,
158
+ arguments: arguments,
159
+ return_value: (tp.return_value rescue nil),
160
+ filepath: filepath,
161
+ line_number: line_number,
162
+ defined_class: tp.defined_class,
163
+ trace: caller[CALLER_START_POINT..(CALLER_START_POINT + options[:with_trace_to])],
164
+ tp: tp
165
+ }
166
+ end
126
167
 
127
168
  def tap_init?(klass, parameters)
128
169
  receiver = parameters[:receiver]
@@ -136,7 +177,7 @@ class TappingDevice
136
177
  end
137
178
 
138
179
  def tap_on?(object, parameters)
139
- parameters[:receiver].object_id == object.object_id
180
+ parameters[:receiver].__id__ == object.__id__
140
181
  end
141
182
 
142
183
  def tap_associations?(object, parameters)
@@ -146,4 +187,24 @@ class TappingDevice
146
187
  associations = model_class.reflections
147
188
  associations.keys.include?(parameters[:method_name].to_s)
148
189
  end
190
+
191
+ def process_options(options)
192
+ options[:filter_by_paths] ||= []
193
+ options[:exclude_by_paths] ||= []
194
+ options[:with_trace_to] ||= 50
195
+ options[:root_device] ||= self
196
+ options[:descendants] ||= []
197
+ options
198
+ end
199
+
200
+ def stop_if_condition_fulfilled(yield_parameters)
201
+ if @stop_when&.call(yield_parameters)
202
+ stop!
203
+ root_device.stop!
204
+ end
205
+ end
206
+
207
+ def is_from_target?(object, tp)
208
+ object.__id__ == tp.self.__id__
209
+ end
149
210
  end
@@ -26,14 +26,14 @@ Gem::Specification.new do |spec|
26
26
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
27
  spec.require_paths = ["lib"]
28
28
 
29
- if ENV["RAILS_VERSION"].to_i >= 6
29
+ if ENV["RAILS_VERSION"]
30
30
  spec.add_dependency "activerecord", "~> #{ENV["RAILS_VERSION"]}"
31
- spec.add_development_dependency "sqlite3", "~> 1.4.1"
32
31
  else
33
- spec.add_dependency "activerecord", "~> 5.2"
34
- spec.add_development_dependency "sqlite3", "~> 1.3.6"
32
+ spec.add_dependency "activerecord", ">= 5.2"
35
33
  end
36
34
 
35
+ spec.add_development_dependency "sqlite3", ">= 1.3.6"
36
+ spec.add_development_dependency "database_cleaner"
37
37
  spec.add_development_dependency "bundler", "~> 2.0"
38
38
  spec.add_development_dependency "pry"
39
39
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,43 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapping_device
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - st0012
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-03 00:00:00.000000000 Z
11
+ date: 2019-11-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '5.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: sqlite3
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: 1.3.6
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.3.6
41
+ - !ruby/object:Gem::Dependency
42
+ name: database_cleaner
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -116,6 +130,8 @@ files:
116
130
  - bin/setup
117
131
  - lib/tapping_device.rb
118
132
  - lib/tapping_device/exceptions.rb
133
+ - lib/tapping_device/sql_listener.rb
134
+ - lib/tapping_device/sql_tapping_methods.rb
119
135
  - lib/tapping_device/trackable.rb
120
136
  - lib/tapping_device/version.rb
121
137
  - tapping_device.gemspec