columns_trace 0.1.0 → 0.2.0

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: 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