tapping_device 0.1.0 → 0.1.1

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: 145b8d4cd78d7f40b318352ff3a39a6a4aa194dee91e84db5e4946c522bd59b9
4
- data.tar.gz: 9978287948da8f54fc96699be31059abd8ad7d643bf05cb9c54d43bc5ff06019
3
+ metadata.gz: 31bb339b2993e3656c953c0c403b4145d3047722919f86ab4d0683c577379027
4
+ data.tar.gz: 76b0b6fab4f3998173960d3a0bce5bdc6faa8155bc49fe4ec3a0ecd0196e8feb
5
5
  SHA512:
6
- metadata.gz: 7870a46e126309662fbdd4c7fea746b9bac8b4c31583061e7ed068f77c6d2d6960b84601f74172cb3bb289d615487c513468f1c18ee62f868eb4d229652e099d
7
- data.tar.gz: 3b59a134c7e30ce2f5d2f1e3f6bdc6ad76b5fccac4e8092f5ae441c11858d1d3b0ad035b3e5648e0b3912b89da43633e8cc55db749662f1c0e8db8eee4b463d5
6
+ metadata.gz: 7641adbd68125ce2cb1a2fdb96969d09dcedc8d35b0c24fc1e0136badaca586bf7287631c3b6fecc7d3909958e0c49bd2d05d2de3b41845f680fb19165ccab1e
7
+ data.tar.gz: f9d685094c3f45b3e330c2725fc61bc01a9ae11cb5e486a1fec5de9a2131860cd64160e1d83753210529dc3383523d8072437e8ff285fc589b6bb6aafe5fcf5c
@@ -0,0 +1,28 @@
1
+ name: Ruby Gem
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - v*
7
+
8
+ jobs:
9
+ build:
10
+ name: Build + Publish
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@master
15
+ - name: Set up Ruby 2.6
16
+ uses: actions/setup-ruby@v1
17
+ with:
18
+ version: 2.6.x
19
+ - name: Publish to RubyGems
20
+ run: |
21
+ mkdir -p $HOME/.gem
22
+ touch $HOME/.gem/credentials
23
+ chmod 0600 $HOME/.gem/credentials
24
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
25
+ gem build *.gemspec
26
+ gem push *.gem
27
+ env:
28
+ GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
@@ -4,11 +4,12 @@ on: [push]
4
4
 
5
5
  jobs:
6
6
  test:
7
- name: Test on ruby ${{ matrix.ruby_version }}
7
+ name: Test on ruby ${{ matrix.ruby_version }} and rails ${{ matrix.rails_version }}
8
8
  runs-on: ${{ matrix.os }}
9
9
  strategy:
10
10
  matrix:
11
- ruby_version: ['2.4', '2.5', '2.6']
11
+ rails_version: ['5.2', '6']
12
+ ruby_version: ['2.5', '2.6']
12
13
  os: [ubuntu-latest]
13
14
  steps:
14
15
  - uses: actions/checkout@v1
@@ -16,8 +17,13 @@ jobs:
16
17
  uses: actions/setup-ruby@v1
17
18
  with:
18
19
  ruby-version: ${{ matrix.ruby_version }}
20
+ - name: Install sqlite
21
+ run: |
22
+ sudo apt-get install libsqlite3-dev
19
23
 
20
- - name: Build and test with Rake
24
+ - name: Build and test with Rails ${{ matrix.rails_version }}
25
+ env:
26
+ RAILS_VERSION: ${{ matrix.rails_version }}
21
27
  run: |
22
28
  gem install bundler
23
29
  bundle install --jobs 4 --retry 3
data/Gemfile.lock CHANGED
@@ -2,13 +2,29 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  tapping_device (0.1.0)
5
+ activerecord (~> 6.0)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
10
+ activemodel (6.0.0)
11
+ activesupport (= 6.0.0)
12
+ activerecord (6.0.0)
13
+ activemodel (= 6.0.0)
14
+ activesupport (= 6.0.0)
15
+ activesupport (6.0.0)
16
+ concurrent-ruby (~> 1.0, >= 1.0.2)
17
+ i18n (>= 0.7, < 2)
18
+ minitest (~> 5.1)
19
+ tzinfo (~> 1.1)
20
+ zeitwerk (~> 2.1, >= 2.1.8)
9
21
  coderay (1.1.2)
22
+ concurrent-ruby (1.1.5)
10
23
  diff-lcs (1.3)
24
+ i18n (1.7.0)
25
+ concurrent-ruby (~> 1.0)
11
26
  method_source (0.9.2)
27
+ minitest (5.12.2)
12
28
  pry (0.12.2)
13
29
  coderay (~> 1.1.0)
14
30
  method_source (~> 0.9.0)
@@ -26,6 +42,11 @@ GEM
26
42
  diff-lcs (>= 1.2.0, < 2.0)
27
43
  rspec-support (~> 3.8.0)
28
44
  rspec-support (3.8.2)
45
+ sqlite3 (1.4.1)
46
+ thread_safe (0.3.6)
47
+ tzinfo (1.2.5)
48
+ thread_safe (~> 0.1)
49
+ zeitwerk (2.2.0)
29
50
 
30
51
  PLATFORMS
31
52
  ruby
@@ -35,6 +56,7 @@ DEPENDENCIES
35
56
  pry
36
57
  rake (~> 10.0)
37
58
  rspec (~> 3.0)
59
+ sqlite3 (~> 1.4.1)
38
60
  tapping_device!
39
61
 
40
62
  BUNDLED WITH
data/README.md CHANGED
@@ -2,17 +2,14 @@
2
2
 
3
3
  ![](https://github.com/st0012/tapping_device/workflows/Ruby/badge.svg)
4
4
 
5
- `tapping_device` is a gem based on Ruby’s `TracePoint` class that allows you to tap method calls of specified objects. This could be useful for debugging. For example, you can use it to see who calls you `Post` records
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 you `Post` records
6
6
 
7
7
  ```ruby
8
8
  class PostsController < ApplicationController
9
9
  include TappingDevice::Trackable
10
10
 
11
- before_action :set_post, only: [:show, :edit, :update, :destroy]
12
-
13
- # GET /posts/1
14
- # GET /posts/1.json
15
11
  def show
12
+ @post = Post.find(params[:id])
16
13
  tap_on!(@post) do |payload|
17
14
  puts "Method: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
18
15
  end
@@ -23,12 +20,28 @@ end
23
20
  And you can see these in log:
24
21
 
25
22
  ```
26
- Method: name line: /Users/st0012/projects/sample/app/views/posts/show.html.erb:5
27
- Method: user_id line: /Users/st0012/projects/sample/app/views/posts/show.html.erb:10
28
- Method: to_param line: /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
23
+ Method: name line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
24
+ Method: user_id line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
25
+ Method: to_param line: /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
26
+ ```
27
+
28
+ 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
+
30
+ ```ruby
31
+ tap_assoc!(order) do |payload|
32
+ puts "Assoc: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
33
+ end
34
+ ```
35
+
36
+ ```
37
+ Assoc: payments line: /RUBY_PATH/gems/2.6.0/gems/jsonapi-resources-0.9.10/lib/jsonapi/resource.rb:124
38
+ Assoc: line_items line: /MY_PROJECT/app/models/line_item_container_helpers.rb:44
39
+ Assoc: effective_line_items line: /MY_PROJECT/app/models/line_item_container_helpers.rb:110
40
+ Assoc: amending_orders line: /MY_PROJECT/app/models/order.rb:385
41
+ Assoc: amends_order line: /MY_PROJECT/app/models/order.rb:432
29
42
  ```
30
43
 
31
- **Don't use this on production**
44
+ However, depending on the size of your application, tapping any object could **harm the performance significantly**. **Don't use this on production**
32
45
 
33
46
 
34
47
  ## Installation
@@ -55,24 +68,26 @@ $ gem install tapping_device
55
68
  In order to use `tapping_device`, you need to include `TappingDevice::Trackable` module in where you want to track your code.
56
69
 
57
70
  ### Methods
58
- - `tap_initialization_of!(class)` - allows you to track a class’ instance initialization
59
- - shortcut - `tap_init!`
60
- - `tap_calls_on!(object)` - allows you to track any calls received by the object
61
- - shortcut - `tap_on!`
62
- - `stop_tapping!(object)` - this stops tapping on the given object
63
- - shortcut - `untap!`
71
+ - `tap_init!(class)` - tracks a class’ instance initialization
72
+ - `tap_on!(object)` - tracks any calls received by the object
73
+ - `tap_assoc!(activerecord_object)` - tracks association calls on a record, like `post.comments`
74
+ - `untap!(object)` - this stops tapping on the given object
64
75
 
65
76
  ### Info of the call
66
77
  All tapping methods (start with `tap_`) takes a block and yield a hash as block argument.
67
78
 
68
79
  ```ruby
69
- tap_initialization_of!(Student) do |payload|
70
- puts(payload.to_s)
71
- end
72
-
73
- Student.new("Stan", 18)
74
-
75
- #=> {:receiver=>#<Student:0x00007feec04d9e58 @name="Stan", @age=18>, :method_name=>:initialize, :arguments=>[[:name, "Stan"], [:age, 18]], :return_value=>18, :filepath=>"/path/spec/trackable_spec.rb", :line_number=>7, :defined_class=>Student}
80
+ {
81
+ :receiver=>#<Student:0x00007fabed02aeb8 @name="Stan", @age=18, @tapping_device=[#<TracePoint:return `age'@/PROJECT_PATH/tapping_device/spec/trackable_spec.rb:17>]>,
82
+ :method_name=>:age,
83
+ :arguments=>[],
84
+ :return_value=>18,
85
+ :filepath=>"/PROJECT_PATH/tapping_device/spec/trackable_spec.rb",
86
+ :line_number=>"171",
87
+ :defined_class=>Student,
88
+ :trace=>[],
89
+ :tp=>#<TracePoint:return `age'@/PROJECT_PATH/tapping_device/spec/trackable_spec.rb:17>
90
+ }
76
91
  ```
77
92
 
78
93
  The hash contains
@@ -85,33 +100,34 @@ The hash contains
85
100
  - `return_value` - return value of the method call
86
101
  - `filepath` - path to the file that performs the method call
87
102
  - `line_number`
88
- - `defined_class` - in which class that defines the that’s being called
103
+ - `defined_class` - in which class that defines the method being called
89
104
  - `trace` - stack trace of the call. Default is an empty array unless `with_trace_to` option is set
90
105
  - `tp` - trace point object of this call
91
106
 
92
107
 
93
108
  ### Options
94
- - `with_trace_to` - the number of traces we want to put into `trace`. Default is `nil`, so `trace` would be empty
95
- - `exclude_from_paths` - an array of call path patterns that we want to skip. This could be very helpful when working on Rails projects.
109
+ - `with_trace_to: 10` - the number of traces we want to put into `trace`. Default is `nil`, so `trace` would be empty
110
+ - `exclude_by_paths: [/path/]` - an array of call path patterns that we want to skip. This could be very helpful when working on large project like Rails applications.
111
+ - `filter_by_paths: [/path/]` - only contain calls from the specified paths
96
112
 
97
113
  ```ruby
98
- tap_on!(@post, exclude_from_paths: [/active_record/]) do |payload|
114
+ tap_on!(@post, exclude_by_paths: [/active_record/]) do |payload|
99
115
  puts "Method: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
100
116
  end
101
117
  ```
102
118
 
103
119
  ```
104
- Method: _read_attribute line: /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
105
- Method: name line: /Users/st0012/projects/sample/app/views/posts/show.html.erb:5
106
- Method: _read_attribute line: /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
107
- Method: user_id line: /Users/st0012/projects/sample/app/views/posts/show.html.erb:10
120
+ Method: _read_attribute line: /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
121
+ Method: name line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
122
+ Method: _read_attribute line: /RUBY_PATH/gems/2.6.0/gems/activerecord-5.2.0/lib/active_record/attribute_methods/read.rb:40
123
+ Method: user_id line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
108
124
  .......
109
125
 
110
126
  # versus
111
127
 
112
- Method: name line: /Users/st0012/projects/sample/app/views/posts/show.html.erb:5
113
- Method: user_id line: /Users/st0012/projects/sample/app/views/posts/show.html.erb:10
114
- Method: to_param line: /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
128
+ Method: name line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
129
+ Method: user_id line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
130
+ Method: to_param line: /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
115
131
  ```
116
132
 
117
133
 
@@ -150,11 +166,26 @@ end
150
166
  And you can see these in log:
151
167
 
152
168
  ```
153
- Method: name line: /Users/st0012/projects/sample/app/views/posts/show.html.erb:5
154
- Method: user_id line: /Users/st0012/projects/sample/app/views/posts/show.html.erb:10
155
- Method: to_param line: /Users/st0012/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
169
+ Method: name line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:5
170
+ Method: user_id line: /PROJECT_PATH/sample/app/views/posts/show.html.erb:10
171
+ Method: to_param line: /RUBY_PATH/gems/2.6.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:236
156
172
  ```
157
173
 
174
+ ### `tap_assoc!`
175
+
176
+ ```ruby
177
+ tap_assoc!(order) do |payload|
178
+ puts "Assoc: #{payload[:method_name]} line: #{payload[:filepath]}:#{payload[:line_number]}"
179
+ end
180
+ ```
181
+
182
+ ```
183
+ Assoc: payments line: /RUBY_PATH/gems/2.6.0/gems/jsonapi-resources-0.9.10/lib/jsonapi/resource.rb:124
184
+ Assoc: line_items line: /MY_PROJECT/app/models/line_item_container_helpers.rb:44
185
+ Assoc: effective_line_items line: /MY_PROJECT/app/models/line_item_container_helpers.rb:110
186
+ Assoc: amending_orders line: /MY_PROJECT/app/models/order.rb:385
187
+ Assoc: amends_order line: /MY_PROJECT/app/models/order.rb:432
188
+ ```
158
189
 
159
190
 
160
191
  ## Development
@@ -165,7 +196,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
165
196
 
166
197
  ## Contributing
167
198
 
168
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/tapping_device. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
199
+ Bug reports and pull requests are welcome on GitHub at https://github.com/st0012/tapping_device. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
169
200
 
170
201
  ## License
171
202
 
@@ -1,3 +1,5 @@
1
+ require "active_record"
2
+
1
3
  module TappingDevice
2
4
  module Trackable
3
5
  TAPPING_DEVICE = :@tapping_device
@@ -5,15 +7,16 @@ module TappingDevice
5
7
 
6
8
  def tap_initialization_of!(klass, options = {}, &block)
7
9
  raise "argument should be a class, got #{klass}" unless klass.is_a?(Class)
8
- options[:condition] = -> (arguments) { arguments[:method_name] == :initialize && arguments[:defined_class] == klass }
9
- options[:block] = block
10
- track(klass, **options)
10
+ track(klass, condition: :tap_init?, block: block, **options)
11
+ end
12
+
13
+ def tap_association_calls!(record, options = {}, &block)
14
+ raise "argument should be an instance of ActiveRecord::Base" unless record.is_a?(ActiveRecord::Base)
15
+ track(record, condition: :tap_associations?, block: block, **options)
11
16
  end
12
17
 
13
18
  def tap_calls_on!(object, options = {}, &block)
14
- options[:condition] = -> (arguments) { arguments[:receiver].object_id == object.object_id }
15
- options[:block] = block
16
- track(object, **options)
19
+ track(object, condition: :tap_on?, block: block, **options)
17
20
  end
18
21
 
19
22
  def stop_tapping!(object)
@@ -21,17 +24,22 @@ module TappingDevice
21
24
  end
22
25
 
23
26
  alias :tap_init! :tap_initialization_of!
27
+ alias :tap_assoc! :tap_association_calls!
24
28
  alias :tap_on! :tap_calls_on!
25
29
  alias :untap! :stop_tapping!
26
30
 
27
31
  private
28
32
 
29
- def track(object, condition:, block:, with_trace_to: nil, exclude_from_paths: [])
33
+ def track(object, condition:, block:, with_trace_to: nil, exclude_by_paths: [], filter_by_paths: nil)
30
34
  trace_point = TracePoint.trace(:return) do |tp|
31
35
  filepath, line_number = caller(CALLER_START_POINT).first.split(":")[0..1]
32
36
 
33
37
  # this needs to be placed upfront so we can exclude noise before doing more work
34
- next if exclude_from_paths.any? { |pattern| pattern.match?(filepath) }
38
+ next if exclude_by_paths.any? { |pattern| pattern.match?(filepath) }
39
+
40
+ if filter_by_paths
41
+ next unless filter_by_paths.any? { |pattern| pattern.match?(filepath) }
42
+ end
35
43
 
36
44
  arguments = tp.binding.local_variables.map { |n| [n, tp.binding.local_variable_get(n)] }
37
45
 
@@ -49,12 +57,35 @@ module TappingDevice
49
57
 
50
58
  yield_parameters[:trace] = caller[CALLER_START_POINT..(CALLER_START_POINT + with_trace_to)] if with_trace_to
51
59
 
52
- block.call(yield_parameters) if condition.call(yield_parameters)
60
+ block.call(yield_parameters) if send(condition, object, yield_parameters)
53
61
  end
54
62
 
55
63
  add_tapping_device(object, trace_point)
56
64
  end
57
65
 
66
+ def tap_init?(klass, parameters)
67
+ receiver = parameters[:receiver]
68
+ method_name = parameters[:method_name]
69
+
70
+ if klass.ancestors.include?(ActiveRecord::Base)
71
+ method_name == :new && receiver.ancestors.include?(klass)
72
+ else
73
+ method_name == :initialize && receiver.is_a?(klass)
74
+ end
75
+ end
76
+
77
+ def tap_on?(object, parameters)
78
+ parameters[:receiver].object_id == object.object_id
79
+ end
80
+
81
+ def tap_associations?(object, parameters)
82
+ return false unless tap_on?(object, parameters)
83
+
84
+ model_class = object.class
85
+ associations = model_class.reflections
86
+ associations.keys.include?(parameters[:method_name].to_s)
87
+ end
88
+
58
89
  def get_tapping_device(object)
59
90
  object.instance_variable_get(TAPPING_DEVICE)
60
91
  end
@@ -1,3 +1,3 @@
1
1
  module TappingDevice
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -26,6 +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
30
+ spec.add_dependency "activerecord", "~> #{ENV["RAILS_VERSION"]}"
31
+ spec.add_development_dependency "sqlite3", "~> 1.4.1"
32
+ else
33
+ spec.add_dependency "activerecord", "~> 5.2"
34
+ spec.add_development_dependency "sqlite3", "~> 1.3.6"
35
+ end
36
+
29
37
  spec.add_development_dependency "bundler", "~> 2.0"
30
38
  spec.add_development_dependency "pry"
31
39
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapping_device
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - st0012
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-10-19 00:00:00.000000000 Z
11
+ date: 2019-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.3.6
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.3.6
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -73,6 +101,7 @@ executables: []
73
101
  extensions: []
74
102
  extra_rdoc_files: []
75
103
  files:
104
+ - ".github/workflows/gempush.yml"
76
105
  - ".github/workflows/ruby.yml"
77
106
  - ".gitignore"
78
107
  - ".rspec"
@@ -111,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
140
  - !ruby/object:Gem::Version
112
141
  version: '0'
113
142
  requirements: []
114
- rubygems_version: 3.0.6
143
+ rubygems_version: 3.0.3
115
144
  signing_key:
116
145
  specification_version: 4
117
146
  summary: tapping_device provides useful helpers to intercept method calls