columns_trace 0.1.0 → 0.2.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: cd2b5b47e4fa2ca409bf7dd531c8766a6ff6487b50e996f04425577c07a65667
4
- data.tar.gz: 28fa32928a82d9c1df876bad8002d677bd7c7e5f0a001f56ad6170b9b87e8f2c
3
+ metadata.gz: c654a6d0c0772fffa33bb28544ba4e3299272b2673f8f4a138a4526a16a7f036
4
+ data.tar.gz: f8f2d7914dec1d9b1a39182626ba0f37736bdf2b3a618ade7f9cb1b5b204850a
5
5
  SHA512:
6
- metadata.gz: df2740562388acd113084e28f50940d2bbf0d0d934abe0b8b4def515158f2bb36ba29179ae317e7cf77665039e5d77d2c15d76ef044b3e4f56f314f046882ba0
7
- data.tar.gz: 77fcfa0230679ba9efc329225c6eda32f6ad915bc0bee79df227b724bc1c75ba3cb5e7b51943b1142ba36586f74d94cdeec9bc9873daa807f1e009c4c53f8e4f
6
+ metadata.gz: 408421ba16b44441a4be3e24c46b0ef7e31bfbcb58d6eacc229c51882c00bd2fbb2956bf16a51572f8d2fab94a9ed515bebb4e8450efdea64b1e849bcffcffb7
7
+ data.tar.gz: 3ef7b2f0cf92cce13760dd581ef68a59fe833ce1fe646c9c9c5476ed6163f024efad6bf67732c6f4f338bf92a2a50deed2b560c9b923605d68a2d7d482d6c539
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
1
  ## master (unreleased)
2
2
 
3
+ ## 0.2.0 (2023-10-13)
4
+
5
+ - Allow to use custom reporters
6
+
7
+ ```ruby
8
+ class MyReporter
9
+ def report(title, created_records)
10
+ # do actual reporting
11
+ end
12
+ end
13
+
14
+ ColumnsTrace.reporter = MyReporter.new
15
+ ```
16
+
17
+ `ColumnsTrace.logger=` setting is removed in favor of using `ColumnsTrace.reporter=`.
18
+ If you configured custom logger, you should now configure it via:
19
+
20
+ ```ruby
21
+ logger = ActiveSupport::Logger.new("/my/custom/logger.log")
22
+ ColumnsTrace.reporter = ColumnsTrace::LogReporter.new(logger)
23
+ ```
24
+
25
+ ## 0.1.0 (2023-10-04)
26
+
3
27
  - First release
data/README.md CHANGED
@@ -75,10 +75,10 @@ ColumnsTrace.configure do |config|
75
75
  # config.ignored_columns = [:updated_at, { User => :admin }]
76
76
  config.ignored_columns = []
77
77
 
78
- # The logger to log to.
79
- # Defaults to logger, that outputs to `log/columns_trace.log` file
80
- # when the gem is used in the Rails app.
81
- config.logger = nil
78
+ # The reporter that is used for reporting.
79
+ # Defaults to log reporter that outputs to `log/columns_trace.log` file
80
+ # when inside a Rails application.
81
+ config.reporter = nil
82
82
 
83
83
  # Controls the contents of the printed backtrace.
84
84
  # Is set to the default Rails.backtrace_cleaner when the gem is used in the Rails app.
@@ -94,6 +94,31 @@ end
94
94
  ColumnsTrace.enable_sidekiq_tracing!
95
95
  ```
96
96
 
97
+ ### Custom reporters
98
+
99
+ By default offenses are reported to a log reporter that outputs to `log/columns_trace.log` file
100
+ when inside a Rails application.
101
+
102
+ You can set your custom reporter by defining a class responding to `#report` method.
103
+
104
+ ```ruby
105
+ class MyReporter
106
+ def report(title, created_records)
107
+ title # => "controller#action"
108
+ created_records # => [#<ColumnsTrace::CreatedRecord>]
109
+ created_records.each do |record|
110
+ record.model # class of ActiveRecord model
111
+ record.accessed_fields # array of accessed fields
112
+ record.unused_fields # array of unused fields
113
+ record.backtrace # array of strings
114
+ record.record # ActiveRecord model instance
115
+ end
116
+ end
117
+ end
118
+
119
+ ColumnsTrace.reporter = MyReporter.new
120
+ ```
121
+
97
122
  ## Development
98
123
 
99
124
  After checking out the repo, run `bundle install` to install dependencies. Then, run `rake` to run the linter and tests.
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ColumnsTrace
4
+ # Class that is used to store metadata about created ActiveRecord records.
5
+ class CreatedRecord
6
+ # Model class
7
+ # @return [Class]
8
+ #
9
+ attr_reader :model
10
+
11
+ # Model instance
12
+ # @return [ActiveRecord::Base]
13
+ #
14
+ attr_reader :record
15
+
16
+ # Backtrace where the instance was created
17
+ # @return [Array<String>]
18
+ #
19
+ attr_reader :backtrace
20
+
21
+ def initialize(record, backtrace)
22
+ @model = record.class
23
+ @record = record
24
+ @backtrace = backtrace
25
+ end
26
+
27
+ # Get accessed fields on model instance
28
+ # @return [Array<String>]
29
+ #
30
+ def accessed_fields
31
+ @accessed_fields ||= record.accessed_fields
32
+ end
33
+
34
+ # Get unused fields on model instance
35
+ # @return [Array<String>]
36
+ #
37
+ def unused_fields
38
+ # We need to store this into local variable, because `record.attributes`
39
+ # will access all attributes.
40
+ accessed = accessed_fields
41
+ record.attributes.keys - accessed
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ColumnsTrace
4
+ # Reporter that reports into the provided logger.
5
+ class LogReporter
6
+ def initialize(logger)
7
+ @logger = logger
8
+ end
9
+
10
+ # Main reporter's method
11
+ #
12
+ # @param title [String] title of the reporting, e.g. controller action etc
13
+ # @param created_records [Array<ColumnsTrace::CreatedRecord>] items that hold
14
+ # metadata about created records
15
+ #
16
+ def report(title, created_records)
17
+ lines = []
18
+
19
+ created_records.group_by(&:model).each do |model, grouped_created_records|
20
+ lines.concat(lines_for_created_records(model, grouped_created_records))
21
+ end
22
+
23
+ if lines.any?
24
+ @logger.info("#{title}\n#{lines.join("\n")}")
25
+ end
26
+ end
27
+
28
+ private
29
+ def lines_for_created_records(model, created_records)
30
+ lines = []
31
+
32
+ created_records.group_by(&:backtrace).each do |backtrace, grouped_created_records|
33
+ accessed = accessed_fields(grouped_created_records)
34
+ unused = grouped_created_records.first.unused_fields - accessed
35
+ unused.reject! { |column| ColumnsTrace.ignored_column?(model, column) }
36
+
37
+ if unused.any?
38
+ records_text = "record".pluralize(grouped_created_records.size)
39
+ lines << <<-MSG
40
+ #{grouped_created_records.size} #{model.name} #{records_text}: unused columns - #{format_columns(unused)}; used columns - #{format_columns(accessed)}
41
+ ↳ #{backtrace.join("\n ")}
42
+ MSG
43
+ end
44
+ end
45
+
46
+ lines
47
+ end
48
+
49
+ def accessed_fields(created_records)
50
+ created_records.map(&:accessed_fields).flatten.uniq
51
+ end
52
+
53
+ def format_columns(columns)
54
+ columns.map(&:inspect).join(", ")
55
+ end
56
+ end
57
+ end
@@ -15,11 +15,17 @@ module ColumnsTrace
15
15
 
16
16
  ActiveSupport.on_load(:action_controller) do
17
17
  before_action { Registry.clear }
18
- after_action { Reporter.report("#{self.class.name}##{action_name}") }
18
+
19
+ after_action do
20
+ ColumnsTrace.reporter.report("#{self.class.name}##{action_name}", Registry.created_records)
21
+ end
19
22
  end
20
23
 
21
24
  ActiveSupport.on_load(:active_job) do
22
25
  before_perform { Registry.clear }
23
- after_perform { Reporter.report(self.class.name) }
26
+
27
+ after_perform do
28
+ ColumnsTrace.reporter.report(self.class.name, Registry.created_records)
29
+ end
24
30
  end
25
31
  end
@@ -5,7 +5,9 @@ module ColumnsTrace
5
5
  class Railtie < Rails::Railtie
6
6
  initializer "columns_trace.set_configs" do
7
7
  ColumnsTrace.backtrace_cleaner = Rails.backtrace_cleaner
8
- ColumnsTrace.logger = ActiveSupport::Logger.new(Rails.root.join("log", "columns_trace.log"))
8
+
9
+ logger = ActiveSupport::Logger.new(Rails.root.join("log", "columns_trace.log"))
10
+ ColumnsTrace.reporter = LogReporter.new(logger)
9
11
  end
10
12
  end
11
13
  end
@@ -4,18 +4,16 @@ module ColumnsTrace
4
4
  # @private
5
5
  # Note: can use ActiveSupport::IsolatedExecutionState instead of this module for rails 7.0+.
6
6
  module Registry
7
- Entry = Struct.new(:model, :record, :backtrace)
8
-
9
7
  class << self
10
8
  def register(record, backtrace)
11
- state << Entry.new(record.class, record, backtrace)
9
+ state << CreatedRecord.new(record, backtrace)
12
10
  end
13
11
 
14
12
  def clear
15
13
  state.clear
16
14
  end
17
15
 
18
- def entries
16
+ def created_records
19
17
  state
20
18
  end
21
19
 
@@ -6,7 +6,7 @@ module ColumnsTrace
6
6
  def call(worker, _job, _queue)
7
7
  Registry.clear
8
8
  yield
9
- Reporter.report(worker.class.name)
9
+ ColumnsTrace.reporter.report(worker.class.name, Registry.created_records)
10
10
  end
11
11
  end
12
12
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ColumnsTrace
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/columns_trace.rb CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  require "active_record"
4
4
 
5
+ require_relative "columns_trace/created_record"
5
6
  require_relative "columns_trace/registry"
6
7
  require_relative "columns_trace/rails_integration"
7
- require_relative "columns_trace/reporter"
8
+ require_relative "columns_trace/log_reporter"
8
9
  require_relative "columns_trace/version"
9
10
  require_relative "columns_trace/railtie" if defined?(Rails)
10
11
 
@@ -54,11 +55,11 @@ module ColumnsTrace
54
55
  end
55
56
  end
56
57
 
57
- # Allows to set the logger.
58
- # Defaults to logger, that outputs to `log/columns_trace.log` file
58
+ # Allows to set the reporter.
59
+ # Defaults to log reporter that outputs to `log/columns_trace.log` file
59
60
  # when inside a rails application.
60
61
  #
61
- attr_accessor :logger
62
+ attr_accessor :reporter
62
63
 
63
64
  # @private
64
65
  attr_reader :backtrace_cleaner
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: columns_trace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fatkodima
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-04 00:00:00.000000000 Z
11
+ date: 2023-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -35,10 +35,11 @@ files:
35
35
  - LICENSE.txt
36
36
  - README.md
37
37
  - lib/columns_trace.rb
38
+ - lib/columns_trace/created_record.rb
39
+ - lib/columns_trace/log_reporter.rb
38
40
  - lib/columns_trace/rails_integration.rb
39
41
  - lib/columns_trace/railtie.rb
40
42
  - lib/columns_trace/registry.rb
41
- - lib/columns_trace/reporter.rb
42
43
  - lib/columns_trace/sidekiq_integration.rb
43
44
  - lib/columns_trace/version.rb
44
45
  - test/dummy/tmp/development_secret.txt
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ColumnsTrace
4
- # @private
5
- class Reporter
6
- def self.report(title)
7
- new.report(title)
8
- end
9
-
10
- def report(title)
11
- lines = []
12
-
13
- Registry.entries.group_by(&:model).each do |model, entries|
14
- lines.concat(lines_for_entries(model, entries))
15
- end
16
-
17
- if lines.any?
18
- ColumnsTrace.logger.info("#{title}\n#{lines.join("\n")}")
19
- end
20
- end
21
-
22
- private
23
- def lines_for_entries(model, entries)
24
- lines = []
25
-
26
- entries.group_by(&:backtrace).each do |backtrace, grouped_entries|
27
- records = grouped_entries.map(&:record)
28
-
29
- accessed = accessed_fields(records)
30
- unused = records.first.attributes.keys - accessed
31
- unused.reject! { |column| ColumnsTrace.ignored_column?(model, column) }
32
-
33
- if unused.any?
34
- records_text = "record".pluralize(records.size)
35
- lines << <<-MSG
36
- #{records.size} #{model.name} #{records_text}: unused columns - #{format_columns(unused)}; used columns - #{format_columns(accessed)}
37
- ↳ #{backtrace.join("\n ")}
38
- MSG
39
- end
40
- end
41
-
42
- lines
43
- end
44
-
45
- def accessed_fields(records)
46
- records.map(&:accessed_fields).flatten.uniq
47
- end
48
-
49
- def format_columns(columns)
50
- columns.map(&:inspect).join(", ")
51
- end
52
- end
53
- end