tapping_device 0.5.4 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 231efa59a44f630df303db52d0d3bb460698c02a116374838e4bad4d71d7f69e
4
- data.tar.gz: ca80df8a0ea00350c1e074628425848b5cba4d7f53a7aa4176947d55a3164929
3
+ metadata.gz: 9700574881872ce91eedf976fd5fa7c8506acfa1c0ea8819dc15eeef6e519a9b
4
+ data.tar.gz: 71b65b11ed57273a0c061f23141d5da92078a16f9a7c7f75e9aebb2de71c32c4
5
5
  SHA512:
6
- metadata.gz: 77131aab9e85a2661694ee57470b9067ee44d479d574cbe395eefabd7f88350eaf31071c4ed0d117b1608856e9e6beff9d7ccae592a665c8969231425dafdc43
7
- data.tar.gz: c06dedd14da7798cf93d80ae0d4866928c9e2522184f453588691fdf1a3220b21cfdf953f04575f1015cc1cb58c5bcb2cb5673fd9384cf5515855d048d0bbdb3
6
+ metadata.gz: 7ead5f7a86fd942005a85394a0b59e309fc41bcf625369006ec848d2688722cf5fcc8651ad925c103536bef49e1d0a50f4aab75a177242c48873bc5703aea8cd
7
+ data.tar.gz: f7cab27f0e27937f8c5a8f8c08283ae376e5b7880dffa21857e48c3bcf6e17ed1cb265b5ef399b2f2d294bf5af97b6b26aef16589f97f502c9ebfa29178de0a8
@@ -1,6 +1,11 @@
1
1
  name: Ruby
2
2
 
3
- on: [push, pull_request]
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - master
8
+ pull_request:
4
9
 
5
10
  jobs:
6
11
  test:
@@ -8,9 +13,12 @@ jobs:
8
13
  runs-on: ${{ matrix.os }}
9
14
  strategy:
10
15
  matrix:
11
- rails_version: ['5.2', '6']
12
- ruby_version: ['2.5', '2.6']
16
+ rails_version: ['5.2', '6.0.0', '6.1.0']
17
+ ruby_version: ['2.7', '3.0']
13
18
  os: [ubuntu-latest]
19
+ exclude:
20
+ - ruby_version: '3.0'
21
+ rails_version: '5.2'
14
22
  steps:
15
23
  - uses: actions/checkout@v1
16
24
 
@@ -34,7 +42,9 @@ jobs:
34
42
  bundle install --jobs 4 --retry 3
35
43
 
36
44
  - name: Run test with Rails ${{ matrix.rails_version }}
37
- run: bundle exec rake
45
+ env:
46
+ RAILS_VERSION: ${{ matrix.rails_version }}
47
+ run: make test
38
48
 
39
49
  - name: Publish Test Coverage
40
50
  uses: paambaati/codeclimate-action@v2.6.0
data/.gitignore CHANGED
@@ -7,5 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
 
10
+ Gemfile.lock
11
+
10
12
  # rspec failure tracking
11
13
  .rspec_status
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.5
1
+ 3.0.1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.6.0](https://github.com/st0012/tapping_device/tree/v0.6.0) (2021-04-25)
4
+
5
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.7...v0.6.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Support Ruby 3.0 [\#71](https://github.com/st0012/tapping_device/pull/71) ([st0012](https://github.com/st0012))
10
+
11
+ **Merged pull requests:**
12
+
13
+ - Drop activerecord requirement [\#73](https://github.com/st0012/tapping_device/pull/73) ([st0012](https://github.com/st0012))
14
+ - Improve file-writing tests [\#72](https://github.com/st0012/tapping_device/pull/72) ([st0012](https://github.com/st0012))
15
+ - Simplify output logic with Ruby' Logger class [\#70](https://github.com/st0012/tapping_device/pull/70) ([st0012](https://github.com/st0012))
16
+ - Refactor Payload classes [\#68](https://github.com/st0012/tapping_device/pull/68) ([st0012](https://github.com/st0012))
17
+
18
+ ## [v0.5.7](https://github.com/st0012/tapping_device/tree/v0.5.7) (2020-09-09)
19
+
20
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.6...v0.5.7)
21
+
22
+ **Implemented enhancements:**
23
+
24
+ - Use pastel in output payload [\#62](https://github.com/st0012/tapping_device/issues/62)
25
+
26
+ **Closed issues:**
27
+
28
+ - Support tag option [\#64](https://github.com/st0012/tapping_device/issues/64)
29
+
30
+ **Merged pull requests:**
31
+
32
+ - Use pastel to replace handmade colorizing logic [\#66](https://github.com/st0012/tapping_device/pull/66) ([st0012](https://github.com/st0012))
33
+ - Add tag option [\#65](https://github.com/st0012/tapping_device/pull/65) ([st0012](https://github.com/st0012))
34
+
35
+ ## [v0.5.6](https://github.com/st0012/tapping_device/tree/v0.5.6) (2020-07-17)
36
+
37
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.5...v0.5.6)
38
+
39
+ ## [v0.5.5](https://github.com/st0012/tapping_device/tree/v0.5.5) (2020-07-16)
40
+
41
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.4...v0.5.5)
42
+
43
+ **Fixed bugs:**
44
+
45
+ - InitializationTracker's logic can cause error [\#60](https://github.com/st0012/tapping_device/issues/60)
46
+
47
+ **Closed issues:**
48
+
49
+ - Refactor get\_method\_from\_object [\#59](https://github.com/st0012/tapping_device/issues/59)
50
+
51
+ **Merged pull requests:**
52
+
53
+ - Fix init tracker [\#61](https://github.com/st0012/tapping_device/pull/61) ([st0012](https://github.com/st0012))
54
+
55
+ ## [v0.5.4](https://github.com/st0012/tapping_device/tree/v0.5.4) (2020-07-05)
56
+
57
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.3...v0.5.4)
58
+
59
+ **Closed issues:**
60
+
61
+ - Add with\_print\_calls method [\#52](https://github.com/st0012/tapping_device/issues/52)
62
+ - Tapping any instance of class [\#51](https://github.com/st0012/tapping_device/issues/51)
63
+ - Add ignore\_private option [\#50](https://github.com/st0012/tapping_device/issues/50)
64
+
65
+ **Merged pull requests:**
66
+
67
+ - Restructure README.md [\#58](https://github.com/st0012/tapping_device/pull/58) ([st0012](https://github.com/st0012))
68
+ - Better support on private methods [\#57](https://github.com/st0012/tapping_device/pull/57) ([st0012](https://github.com/st0012))
69
+ - Add with\_\* helpers \(e.g. with\_print\_calls\) [\#56](https://github.com/st0012/tapping_device/pull/56) ([st0012](https://github.com/st0012))
70
+ - Add force\_recording option for debugging [\#55](https://github.com/st0012/tapping_device/pull/55) ([st0012](https://github.com/st0012))
71
+ - Add print\_instance\_\* and write\_instance\_\* helpers [\#54](https://github.com/st0012/tapping_device/pull/54) ([st0012](https://github.com/st0012))
72
+ - Fix tap\_init by adding c\_\* event type [\#53](https://github.com/st0012/tapping_device/pull/53) ([st0012](https://github.com/st0012))
73
+
3
74
  ## [v0.5.3](https://github.com/st0012/tapping_device/tree/v0.5.3) (2020-06-21)
4
75
 
5
76
  [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.2...v0.5.3)
data/Gemfile CHANGED
@@ -2,3 +2,20 @@ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in tapping_device.gemspec
4
4
  gemspec
5
+
6
+ rails_version = ENV["RAILS_VERSION"]
7
+ rails_version = "6.1.0" if rails_version.nil?
8
+
9
+ if rails_version.to_f < 6
10
+ gem "sqlite3", "~> 1.3.0"
11
+ else
12
+ gem "sqlite3"
13
+ end
14
+
15
+ gem "activerecord", "~> #{rails_version}"
16
+
17
+ gem "rake", "~> 13.0"
18
+ gem "rspec", "~> 3.0"
19
+ gem "simplecov", "~> 0.17.1"
20
+ gem "database_cleaner", "~> 2.0.0"
21
+ gem "pry"
data/Makefile ADDED
@@ -0,0 +1,3 @@
1
+ test:
2
+ bundle exec rspec
3
+ WITH_ACTIVE_RECORD=true bundle exec rspec spec/active_record_spec.rb
@@ -1,13 +1,11 @@
1
- require "active_record"
2
- require "active_support/core_ext/module/introspection"
3
- require "pry" # for using Method#source
1
+ require "method_source" # for using Method#source
4
2
 
5
3
  require "tapping_device/version"
6
4
  require "tapping_device/manageable"
7
5
  require "tapping_device/payload"
8
6
  require "tapping_device/output"
9
7
  require "tapping_device/trackable"
10
- require "tapping_device/configurable"
8
+ require "tapping_device/configuration"
11
9
  require "tapping_device/exceptions"
12
10
  require "tapping_device/method_hijacker"
13
11
  require "tapping_device/trackers/initialization_tracker"
@@ -28,7 +26,6 @@ class TappingDevice
28
26
 
29
27
  extend Manageable
30
28
 
31
- include Configurable
32
29
  include Output::Helpers
33
30
 
34
31
  def initialize(options = {}, &block)
@@ -120,8 +117,10 @@ class TappingDevice
120
117
 
121
118
  # this needs to be placed upfront so we can exclude noise before doing more work
122
119
  def should_be_skipped_by_paths?(filepath)
123
- options[:exclude_by_paths].any? { |pattern| pattern.match?(filepath) } ||
124
- (options[:filter_by_paths].present? && !options[:filter_by_paths].any? { |pattern| pattern.match?(filepath) })
120
+ exclude_by_paths = options[:exclude_by_paths]
121
+ filter_by_paths = options[:filter_by_paths]
122
+ exclude_by_paths.any? { |pattern| pattern.match?(filepath) } ||
123
+ (filter_by_paths && !filter_by_paths.empty? && !filter_by_paths.any? { |pattern| pattern.match?(filepath) })
125
124
  end
126
125
 
127
126
  def is_tapping_device_call?(tp)
@@ -137,11 +136,11 @@ class TappingDevice
137
136
  end
138
137
 
139
138
  def with_condition_satisfied?(payload)
140
- @with_condition.blank? || @with_condition.call(payload)
139
+ @with_condition.nil? || @with_condition.call(payload)
141
140
  end
142
141
 
143
142
  def build_payload(tp:, filepath:, line_number:)
144
- Payload.init({
143
+ Payload.new(
145
144
  target: @target,
146
145
  receiver: tp.self,
147
146
  method_name: tp.callee_id,
@@ -152,16 +151,14 @@ class TappingDevice
152
151
  line_number: line_number,
153
152
  defined_class: tp.defined_class,
154
153
  trace: get_traces(tp),
155
- is_private_call?: tp.defined_class.private_method_defined?(tp.callee_id),
154
+ is_private_call: tp.defined_class.private_method_defined?(tp.callee_id),
155
+ tag: options[:tag],
156
156
  tp: tp
157
- })
157
+ )
158
158
  end
159
159
 
160
160
  def get_method_object_from(target, method_name)
161
- target.method(method_name)
162
- rescue ArgumentError
163
- method_method = Object.method(:method).unbind
164
- method_method.bind(target).call(method_name)
161
+ Object.instance_method(:method).bind(target).call(method_name)
165
162
  rescue NameError
166
163
  # if any part of the program uses Refinement to extend its methods
167
164
  # we might still get NoMethodError when trying to get that method outside the scope
@@ -1,10 +1,5 @@
1
- require "active_support/configurable"
2
- require "active_support/concern"
3
-
4
1
  class TappingDevice
5
- module Configurable
6
- extend ActiveSupport::Concern
7
-
2
+ class Configuration
8
3
  DEFAULTS = {
9
4
  filter_by_paths: [],
10
5
  exclude_by_paths: [],
@@ -16,12 +11,24 @@ class TappingDevice
16
11
  only_private: false
17
12
  }.merge(TappingDevice::Output::DEFAULT_OPTIONS)
18
13
 
19
- included do
20
- include ActiveSupport::Configurable
14
+ def initialize
15
+ @options = {}
21
16
 
22
17
  DEFAULTS.each do |key, value|
23
- config[key] = value
18
+ @options[key] = value
24
19
  end
25
20
  end
21
+
22
+ def [](key)
23
+ @options[key]
24
+ end
25
+
26
+ def []=(key, value)
27
+ @options[key] = value
28
+ end
29
+ end
30
+
31
+ def self.config
32
+ @config ||= Configuration.new
26
33
  end
27
34
  end
@@ -20,10 +20,14 @@ class TappingDevice
20
20
 
21
21
  def is_writer_method?(method_name)
22
22
  has_definition_source?(method_name) && method_name.match?(/\w+=/) && target.method(method_name).source.match?(/attr_writer|attr_accessor/)
23
+ rescue MethodSource::SourceNotFoundError
24
+ false
23
25
  end
24
26
 
25
27
  def is_reader_method?(method_name)
26
28
  has_definition_source?(method_name) && target.method(method_name).source.match?(/attr_reader|attr_accessor/)
29
+ rescue MethodSource::SourceNotFoundError
30
+ false
27
31
  end
28
32
 
29
33
  def has_definition_source?(method_name)
@@ -1,7 +1,6 @@
1
- require "tapping_device/output/payload"
1
+ require "logger"
2
+ require "tapping_device/output/payload_wrapper"
2
3
  require "tapping_device/output/writer"
3
- require "tapping_device/output/stdout_writer"
4
- require "tapping_device/output/file_writer"
5
4
 
6
5
  class TappingDevice
7
6
  module Output
@@ -13,16 +12,16 @@ class TappingDevice
13
12
 
14
13
  module Helpers
15
14
  def and_write(payload_method = nil, options: {}, &block)
16
- and_output(payload_method, options: options, writer_klass: FileWriter, &block)
15
+ and_output(payload_method, options: options, logger: Logger.new(options[:log_file]), &block)
17
16
  end
18
17
 
19
18
  def and_print(payload_method = nil, options: {}, &block)
20
- and_output(payload_method, options: options, writer_klass: StdoutWriter, &block)
19
+ and_output(payload_method, options: options, logger: Logger.new($stdout), &block)
21
20
  end
22
21
 
23
- def and_output(payload_method = nil, options: {}, writer_klass:, &block)
22
+ def and_output(payload_method = nil, options: {}, logger:, &block)
24
23
  output_block = generate_output_block(payload_method, block)
25
- @output_writer = writer_klass.new(options, output_block)
24
+ @output_writer = Writer.new(options: options, output_block: output_block, logger: logger)
26
25
  self
27
26
  end
28
27
 
@@ -1,17 +1,44 @@
1
+ require "pastel"
2
+
1
3
  class TappingDevice
2
4
  module Output
3
- class Payload < Payload
5
+ class PayloadWrapper
4
6
  UNDEFINED = "[undefined]"
7
+ PRIVATE_MARK = " (private)"
8
+
9
+ PASTEL = Pastel.new
10
+ PASTEL.alias_color(:orange, :bright_red, :bright_yellow)
11
+
12
+ TappingDevice::Payload::ATTRS.each do |attr|
13
+ define_method attr do |options = {}|
14
+ @payload.send(attr)
15
+ end
16
+ end
17
+
18
+ alias :is_private_call? :is_private_call
19
+
20
+ def method_head
21
+ @payload.method_head
22
+ end
23
+
24
+ def location(options = {})
25
+ @payload.location(options)
26
+ end
5
27
 
6
28
  alias :raw_arguments :arguments
7
29
  alias :raw_return_value :return_value
8
30
 
31
+ def initialize(payload)
32
+ @payload = payload
33
+ end
34
+
9
35
  def method_name(options = {})
10
- if is_private_call?
11
- ":#{super(options)} (private)"
12
- else
13
- ":#{super(options)}"
14
- end
36
+ name = ":#{@payload.method_name}"
37
+
38
+ name += " [#{tag}]" if tag
39
+ name += PRIVATE_MARK if is_private_call?
40
+
41
+ name
15
42
  end
16
43
 
17
44
  def arguments(options = {})
@@ -22,29 +49,13 @@ class TappingDevice
22
49
  generate_string_result(raw_return_value, options[:inspect])
23
50
  end
24
51
 
25
- COLOR_CODES = {
26
- green: 10,
27
- yellow: 11,
28
- blue: 12,
29
- megenta: 13,
30
- cyan: 14,
31
- orange: 214
32
- }
33
-
34
- COLORS = COLOR_CODES.each_with_object({}) do |(name, code), hash|
35
- hash[name] = "\u001b[38;5;#{code}m"
36
- end.merge(
37
- reset: "\u001b[0m",
38
- nocolor: ""
39
- )
40
-
41
52
  PAYLOAD_ATTRIBUTES = {
42
- method_name: {symbol: "", color: COLORS[:blue]},
43
- location: {symbol: "from:", color: COLORS[:green]},
44
- return_value: {symbol: "=>", color: COLORS[:megenta]},
45
- arguments: {symbol: "<=", color: COLORS[:orange]},
46
- ivar_changes: {symbol: "changes:\n", color: COLORS[:blue]},
47
- defined_class: {symbol: "#", color: COLORS[:yellow]}
53
+ method_name: {symbol: "", color: :bright_blue},
54
+ location: {symbol: "from:", color: :green},
55
+ return_value: {symbol: "=>", color: :magenta},
56
+ arguments: {symbol: "<=", color: :orange},
57
+ ivar_changes: {symbol: "changes:\n", color: :blue},
58
+ defined_class: {symbol: "#", color: :yellow}
48
59
  }
49
60
 
50
61
  PAYLOAD_ATTRIBUTES.each do |attribute, attribute_options|
@@ -54,10 +65,10 @@ class TappingDevice
54
65
 
55
66
  # regenerate attributes with `colorize: true` support
56
67
  define_method attribute do |options = {}|
57
- call_result = send("original_#{attribute}", options)
68
+ call_result = send("original_#{attribute}", options).to_s
58
69
 
59
70
  if options[:colorize]
60
- "#{color}#{call_result}#{COLORS[:reset]}"
71
+ PASTEL.send(color, call_result)
61
72
  else
62
73
  call_result
63
74
  end
@@ -87,7 +98,7 @@ class TappingDevice
87
98
  return unless arg_name
88
99
 
89
100
  arg_name = ":#{arg_name}"
90
- arg_name = value_with_color(arg_name, :orange) if options[:colorize]
101
+ arg_name = PASTEL.orange(arg_name) if options[:colorize]
91
102
  msg = "Passed as #{arg_name} in '#{defined_class(options)}##{method_name(options)}' at #{location(options)}\n"
92
103
  msg += " > #{method_head}\n" if with_method_head
93
104
  msg
@@ -104,17 +115,17 @@ class TappingDevice
104
115
  end
105
116
 
106
117
  def ivar_changes(options = {})
107
- super.map do |ivar, value_changes|
118
+ @payload.ivar_changes.map do |ivar, value_changes|
108
119
  before = generate_string_result(value_changes[:before], options[:inspect])
109
120
  after = generate_string_result(value_changes[:after], options[:inspect])
110
121
 
111
122
  if options[:colorize]
112
- ivar = "#{COLORS[:orange]}#{ivar}#{COLORS[:reset]}"
113
- before = "#{COLORS[:blue]}#{before.to_s}#{COLORS[:reset]}"
114
- after = "#{COLORS[:blue]}#{after.to_s}#{COLORS[:reset]}"
123
+ ivar = PASTEL.orange(ivar.to_s)
124
+ before = PASTEL.bright_blue(before.to_s)
125
+ after = PASTEL.bright_blue(after.to_s)
115
126
  end
116
127
 
117
- " #{ivar}: #{before.to_s} => #{after.to_s}"
128
+ " #{ivar}: #{before} => #{after}"
118
129
  end.join("\n")
119
130
  end
120
131
 
@@ -130,10 +141,6 @@ class TappingDevice
130
141
 
131
142
  private
132
143
 
133
- def value_with_color(value, color)
134
- "#{COLORS[color]}#{value}#{COLORS[:reset]}"
135
- end
136
-
137
144
  def generate_string_result(obj, inspect)
138
145
  case obj
139
146
  when Array
@@ -1,19 +1,21 @@
1
1
  class TappingDevice
2
2
  module Output
3
3
  class Writer
4
- def initialize(options, output_block)
4
+ def initialize(options:, output_block:, logger:)
5
5
  @options = options
6
6
  @output_block = output_block
7
+ @logger = logger
7
8
  end
8
9
 
9
10
  def write!(payload)
10
- raise NotImplementedError
11
+ output = generate_output(payload)
12
+ @logger << output
11
13
  end
12
14
 
13
15
  private
14
16
 
15
17
  def generate_output(payload)
16
- @output_block.call(Output::Payload.init(payload), @options)
18
+ @output_block.call(PayloadWrapper.new(payload), @options)
17
19
  end
18
20
  end
19
21
  end
@@ -1,22 +1,32 @@
1
1
  class TappingDevice
2
- class Payload < Hash
2
+ class Payload
3
3
  ATTRS = [
4
4
  :target, :receiver, :method_name, :method_object, :arguments, :return_value, :filepath, :line_number,
5
- :defined_class, :trace, :tp, :ivar_changes, :is_private_call?
5
+ :defined_class, :trace, :tag, :tp, :ivar_changes, :is_private_call
6
6
  ]
7
7
 
8
- ATTRS.each do |attr|
9
- define_method attr do |options = {}|
10
- self[attr]
11
- end
12
- end
8
+ attr_accessor(*ATTRS)
9
+
10
+ alias :is_private_call? :is_private_call
13
11
 
14
- def self.init(hash)
15
- h = new
16
- hash.each do |k, v|
17
- h[k] = v
18
- end
19
- h
12
+ def initialize(
13
+ target:, receiver:, method_name:, method_object:, arguments:, return_value:, filepath:, line_number:,
14
+ defined_class:, trace:, tag:, tp:, is_private_call:
15
+ )
16
+ @target = target
17
+ @receiver = receiver
18
+ @method_name = method_name
19
+ @method_object = method_object
20
+ @arguments = arguments
21
+ @return_value = return_value
22
+ @filepath = filepath
23
+ @line_number = line_number
24
+ @defined_class = defined_class
25
+ @trace = trace
26
+ @tag = tag
27
+ @tp = tp
28
+ @ivar_changes = {}
29
+ @is_private_call = is_private_call
20
30
  end
21
31
 
22
32
  def method_head
@@ -36,7 +36,7 @@ class TappingDevice
36
36
  define_method "#{output_action}_instance_#{subject}" do |target_klass, options = {}|
37
37
  collection_proxy = AsyncCollectionProxy.new
38
38
 
39
- tap_init!(target_klass, options) do |payload|
39
+ tap_init!(target_klass, options.merge(force_recording: true)) do |payload|
40
40
  collection_proxy << send(helper_method_name, payload.return_value, options)
41
41
  end
42
42
 
@@ -9,10 +9,19 @@ class TappingDevice
9
9
  @options[:event_type] = [event_type, "c_#{event_type}"]
10
10
  end
11
11
 
12
+ def track(object)
13
+ super
14
+ @is_active_record_model = defined?(ActiveRecord) && target.ancestors.include?(ActiveRecord::Base)
15
+ self
16
+ end
17
+
12
18
  def build_payload(tp:, filepath:, line_number:)
13
19
  payload = super
14
- payload[:return_value] = payload[:receiver]
15
- payload[:receiver] = target
20
+
21
+ return payload if @is_active_record_model
22
+
23
+ payload.return_value = payload.receiver
24
+ payload.receiver = target
16
25
  payload
17
26
  end
18
27
 
@@ -24,8 +33,17 @@ class TappingDevice
24
33
  receiver = tp.self
25
34
  method_name = tp.callee_id
26
35
 
27
- if target.ancestors.include?(ActiveRecord::Base)
28
- method_name == :new && receiver.ancestors.include?(target)
36
+ if @is_active_record_model
37
+ # ActiveRecord redefines model classes' .new method,
38
+ # so instead of calling Model#initialize, it'll actually call Model.new
39
+ # see https://github.com/rails/rails/blob/master/activerecord/lib/active_record/inheritance.rb#L50
40
+ method_name == :new &&
41
+ receiver.is_a?(Class) &&
42
+ # this checks if the model class is the target class or a subclass of it
43
+ receiver.ancestors.include?(target) &&
44
+ # Model.new triggers both c_return and return events. so we should only return in 1 type of the events
45
+ # otherwise the callback will be triggered twice
46
+ tp.event == :return
29
47
  else
30
48
  method_name == :initialize && receiver.is_a?(target)
31
49
  end
@@ -47,7 +47,7 @@ class TappingDevice
47
47
  payload = super
48
48
 
49
49
  if change_capturing_event?(tp)
50
- payload[:ivar_changes] = capture_ivar_changes
50
+ payload.ivar_changes = capture_ivar_changes
51
51
  end
52
52
 
53
53
  payload
@@ -55,15 +55,14 @@ class TappingDevice
55
55
 
56
56
  def capture_ivar_changes
57
57
  changes = {}
58
-
59
58
  additional_keys = @latest_instance_variables.keys - @instance_variables_snapshot.keys
60
59
  additional_keys.each do |key|
61
- changes[key] = {before: Output::Payload::UNDEFINED, after: @latest_instance_variables[key]}
60
+ changes[key] = {before: Output::PayloadWrapper::UNDEFINED, after: @latest_instance_variables[key]}
62
61
  end
63
62
 
64
63
  removed_keys = @instance_variables_snapshot.keys - @latest_instance_variables.keys
65
64
  removed_keys.each do |key|
66
- changes[key] = {before: @instance_variables_snapshot[key], after: Output::Payload::UNDEFINED}
65
+ changes[key] = {before: @instance_variables_snapshot[key], after: Output::PayloadWrapper::UNDEFINED}
67
66
  end
68
67
 
69
68
  remained_keys = @latest_instance_variables.keys - additional_keys
@@ -1,3 +1,3 @@
1
1
  class TappingDevice
2
- VERSION = "0.5.4"
2
+ VERSION = "0.6.1"
3
3
  end
@@ -15,30 +15,15 @@ Gem::Specification.new do |spec|
15
15
 
16
16
  spec.metadata["homepage_uri"] = spec.homepage
17
17
  spec.metadata["source_code_uri"] = "https://github.com/st0012/tapping_device"
18
- spec.metadata["changelog_uri"] = "https://github.com/st0012/tapping_device/releases"
18
+ spec.metadata["changelog_uri"] = "https://github.com/st0012/tapping_device/CHANGELOG.md"
19
19
 
20
20
  # Specify which files should be added to the gem when it is released.
21
21
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
22
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
23
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
24
  end
25
- spec.bindir = "exe"
26
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
25
  spec.require_paths = ["lib"]
28
26
 
29
- if ENV["RAILS_VERSION"]
30
- spec.add_dependency "activerecord", "~> #{ENV["RAILS_VERSION"]}"
31
- else
32
- spec.add_dependency "activerecord", ">= 5.2"
33
- end
34
-
35
- spec.add_dependency "pry" # for using Method#source in MutationTracker
36
- spec.add_dependency "activesupport"
37
-
38
- spec.add_development_dependency "sqlite3", ">= 1.3.6"
39
- spec.add_development_dependency "database_cleaner"
40
- spec.add_development_dependency "bundler", "~> 2.0"
41
- spec.add_development_dependency "rake", "~> 13.0"
42
- spec.add_development_dependency "rspec", "~> 3.0"
43
- spec.add_development_dependency "simplecov", "0.17.1"
27
+ spec.add_dependency "method_source", "~> 1.0.0"
28
+ spec.add_dependency "pastel", "~> 0.7"
44
29
  end
metadata CHANGED
@@ -1,141 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapping_device
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - st0012
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-05 00:00:00.000000000 Z
11
+ date: 2021-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activerecord
14
+ name: method_source
15
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: pry
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: activesupport
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: sqlite3
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: 1.3.6
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: 1.3.6
69
- - !ruby/object:Gem::Dependency
70
- name: database_cleaner
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: bundler
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '2.0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
16
  requirements:
94
17
  - - "~>"
95
18
  - !ruby/object:Gem::Version
96
- version: '2.0'
97
- - !ruby/object:Gem::Dependency
98
- name: rake
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '13.0'
104
- type: :development
19
+ version: 1.0.0
20
+ type: :runtime
105
21
  prerelease: false
106
22
  version_requirements: !ruby/object:Gem::Requirement
107
23
  requirements:
108
24
  - - "~>"
109
25
  - !ruby/object:Gem::Version
110
- version: '13.0'
26
+ version: 1.0.0
111
27
  - !ruby/object:Gem::Dependency
112
- name: rspec
28
+ name: pastel
113
29
  requirement: !ruby/object:Gem::Requirement
114
30
  requirements:
115
31
  - - "~>"
116
32
  - !ruby/object:Gem::Version
117
- version: '3.0'
118
- type: :development
33
+ version: '0.7'
34
+ type: :runtime
119
35
  prerelease: false
120
36
  version_requirements: !ruby/object:Gem::Requirement
121
37
  requirements:
122
38
  - - "~>"
123
39
  - !ruby/object:Gem::Version
124
- version: '3.0'
125
- - !ruby/object:Gem::Dependency
126
- name: simplecov
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - '='
130
- - !ruby/object:Gem::Version
131
- version: 0.17.1
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - '='
137
- - !ruby/object:Gem::Version
138
- version: 0.17.1
40
+ version: '0.7'
139
41
  description: tapping_device lets you understand what your Ruby objects do without
140
42
  digging into the code
141
43
  email:
@@ -154,8 +56,8 @@ files:
154
56
  - CHANGELOG.md
155
57
  - CODE_OF_CONDUCT.md
156
58
  - Gemfile
157
- - Gemfile.lock
158
59
  - LICENSE.txt
60
+ - Makefile
159
61
  - README.md
160
62
  - Rakefile
161
63
  - bin/console
@@ -165,14 +67,12 @@ files:
165
67
  - images/print_mutations.png
166
68
  - images/print_traces.png
167
69
  - lib/tapping_device.rb
168
- - lib/tapping_device/configurable.rb
70
+ - lib/tapping_device/configuration.rb
169
71
  - lib/tapping_device/exceptions.rb
170
72
  - lib/tapping_device/manageable.rb
171
73
  - lib/tapping_device/method_hijacker.rb
172
74
  - lib/tapping_device/output.rb
173
- - lib/tapping_device/output/file_writer.rb
174
- - lib/tapping_device/output/payload.rb
175
- - lib/tapping_device/output/stdout_writer.rb
75
+ - lib/tapping_device/output/payload_wrapper.rb
176
76
  - lib/tapping_device/output/writer.rb
177
77
  - lib/tapping_device/payload.rb
178
78
  - lib/tapping_device/trackable.rb
@@ -189,7 +89,7 @@ licenses:
189
89
  metadata:
190
90
  homepage_uri: https://github.com/st0012/tapping_device
191
91
  source_code_uri: https://github.com/st0012/tapping_device
192
- changelog_uri: https://github.com/st0012/tapping_device/releases
92
+ changelog_uri: https://github.com/st0012/tapping_device/CHANGELOG.md
193
93
  post_install_message:
194
94
  rdoc_options: []
195
95
  require_paths:
@@ -205,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
205
105
  - !ruby/object:Gem::Version
206
106
  version: '0'
207
107
  requirements: []
208
- rubygems_version: 3.0.3
108
+ rubygems_version: 3.2.15
209
109
  signing_key:
210
110
  specification_version: 4
211
111
  summary: tapping_device lets you understand what your Ruby objects do without digging
data/Gemfile.lock DELETED
@@ -1,74 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- tapping_device (0.5.4)
5
- activerecord (>= 5.2)
6
- activesupport
7
- pry
8
-
9
- GEM
10
- remote: https://rubygems.org/
11
- specs:
12
- activemodel (6.0.3.2)
13
- activesupport (= 6.0.3.2)
14
- activerecord (6.0.3.2)
15
- activemodel (= 6.0.3.2)
16
- activesupport (= 6.0.3.2)
17
- activesupport (6.0.3.2)
18
- concurrent-ruby (~> 1.0, >= 1.0.2)
19
- i18n (>= 0.7, < 2)
20
- minitest (~> 5.1)
21
- tzinfo (~> 1.1)
22
- zeitwerk (~> 2.2, >= 2.2.2)
23
- coderay (1.1.3)
24
- concurrent-ruby (1.1.6)
25
- database_cleaner (1.7.0)
26
- diff-lcs (1.3)
27
- docile (1.3.2)
28
- i18n (1.8.3)
29
- concurrent-ruby (~> 1.0)
30
- json (2.3.0)
31
- method_source (1.0.0)
32
- minitest (5.14.1)
33
- pry (0.13.1)
34
- coderay (~> 1.1)
35
- method_source (~> 1.0)
36
- rake (13.0.1)
37
- rspec (3.8.0)
38
- rspec-core (~> 3.8.0)
39
- rspec-expectations (~> 3.8.0)
40
- rspec-mocks (~> 3.8.0)
41
- rspec-core (3.8.2)
42
- rspec-support (~> 3.8.0)
43
- rspec-expectations (3.8.4)
44
- diff-lcs (>= 1.2.0, < 2.0)
45
- rspec-support (~> 3.8.0)
46
- rspec-mocks (3.8.1)
47
- diff-lcs (>= 1.2.0, < 2.0)
48
- rspec-support (~> 3.8.0)
49
- rspec-support (3.8.2)
50
- simplecov (0.17.1)
51
- docile (~> 1.1)
52
- json (>= 1.8, < 3)
53
- simplecov-html (~> 0.10.0)
54
- simplecov-html (0.10.2)
55
- sqlite3 (1.4.1)
56
- thread_safe (0.3.6)
57
- tzinfo (1.2.7)
58
- thread_safe (~> 0.1)
59
- zeitwerk (2.3.1)
60
-
61
- PLATFORMS
62
- ruby
63
-
64
- DEPENDENCIES
65
- bundler (~> 2.0)
66
- database_cleaner
67
- rake (~> 13.0)
68
- rspec (~> 3.0)
69
- simplecov (= 0.17.1)
70
- sqlite3 (>= 1.3.6)
71
- tapping_device!
72
-
73
- BUNDLED WITH
74
- 2.1.1
@@ -1,21 +0,0 @@
1
- class TappingDevice
2
- module Output
3
- class FileWriter < Writer
4
- def initialize(options, output_block)
5
- @path = options[:log_file]
6
-
7
- File.write(@path, "") # clean file
8
-
9
- super
10
- end
11
-
12
- def write!(payload)
13
- output = generate_output(payload)
14
-
15
- File.open(@path, "a") do |f|
16
- f << output
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,9 +0,0 @@
1
- class TappingDevice
2
- module Output
3
- class StdoutWriter < Writer
4
- def write!(payload)
5
- puts(generate_output(payload))
6
- end
7
- end
8
- end
9
- end