columns_trace 0.1.0 → 0.3.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: 9b9167813d7f9a2089f1eb2380bc62de8289384ed56f33fc4983ca716b76c0a9
4
+ data.tar.gz: 0ce77996e83a1af64bb9d64acc7231965e1a46263b9be30bb870a3e3a0a6a140
5
5
  SHA512:
6
- metadata.gz: df2740562388acd113084e28f50940d2bbf0d0d934abe0b8b4def515158f2bb36ba29179ae317e7cf77665039e5d77d2c15d76ef044b3e4f56f314f046882ba0
7
- data.tar.gz: 77fcfa0230679ba9efc329225c6eda32f6ad915bc0bee79df227b724bc1c75ba3cb5e7b51943b1142ba36586f74d94cdeec9bc9873daa807f1e009c4c53f8e4f
6
+ metadata.gz: a2f63e4cc55f26f7f86670c65378f203e8ea4e703ccd8079e5a4c201729eddb2de42277f654108ef80b61dc29c31ba5211a97fec44f05076480234e65a3221b5
7
+ data.tar.gz: 3fbddc61ae15705ecb191b3d1856f726b292c59d6973b80c4f464555765e423551572fea0e17a21e3019839b9b109e1f0803fc86db588ab9ac3752da8f3d6a3b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
1
1
  ## master (unreleased)
2
2
 
3
+ ## 0.3.0 (2023-11-09)
4
+
5
+ - Add ability to trace columns usage in custom code
6
+ - Trace unused columns in mailers
7
+
8
+ ## 0.2.0 (2023-10-13)
9
+
10
+ - Allow to use custom reporters
11
+
12
+ ```ruby
13
+ class MyReporter
14
+ def report(title, created_records)
15
+ # do actual reporting
16
+ end
17
+ end
18
+
19
+ ColumnsTrace.reporter = MyReporter.new
20
+ ```
21
+
22
+ `ColumnsTrace.logger=` setting is removed in favor of using `ColumnsTrace.reporter=`.
23
+ If you configured custom logger, you should now configure it via:
24
+
25
+ ```ruby
26
+ logger = ActiveSupport::Logger.new("/my/custom/logger.log")
27
+ ColumnsTrace.reporter = ColumnsTrace::LogReporter.new(logger)
28
+ ```
29
+
30
+ ## 0.1.0 (2023-10-04)
31
+
3
32
  - First release
data/README.md CHANGED
@@ -25,7 +25,7 @@ $ bundle install
25
25
 
26
26
  ## Usage
27
27
 
28
- Hit a controller action or run `ActiveJob` (or `Sidekiq`) job, open `log/columns_trace.log`,
28
+ Hit a controller or email action or run `ActiveJob` (or `Sidekiq`) job, open `log/columns_trace.log`,
29
29
  and see the output:
30
30
 
31
31
  ```
@@ -54,6 +54,18 @@ ImportProjectJob
54
54
  ↳ app/jobs/import_project_job.rb:24:in `perform'
55
55
  ```
56
56
 
57
+ ### Tracing custom code
58
+
59
+ To get columns usage in the custom code, you can manually wrap it by `ColumnsTrace.report`:
60
+
61
+ ```ruby
62
+ task my_rake_task: :environment do
63
+ ColumnsTrace.report("my_rake_task") do
64
+ # do stuff
65
+ end
66
+ end
67
+ ```
68
+
57
69
  ## Configuration
58
70
 
59
71
  You can override the following default options:
@@ -75,10 +87,10 @@ ColumnsTrace.configure do |config|
75
87
  # config.ignored_columns = [:updated_at, { User => :admin }]
76
88
  config.ignored_columns = []
77
89
 
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
90
+ # The reporter that is used for reporting.
91
+ # Defaults to log reporter that outputs to `log/columns_trace.log` file
92
+ # when inside a Rails application.
93
+ config.reporter = nil
82
94
 
83
95
  # Controls the contents of the printed backtrace.
84
96
  # Is set to the default Rails.backtrace_cleaner when the gem is used in the Rails app.
@@ -94,6 +106,31 @@ end
94
106
  ColumnsTrace.enable_sidekiq_tracing!
95
107
  ```
96
108
 
109
+ ### Custom reporters
110
+
111
+ By default offenses are reported to a log reporter that outputs to `log/columns_trace.log` file
112
+ when inside a Rails application.
113
+
114
+ You can set your custom reporter by defining a class responding to `#report` method.
115
+
116
+ ```ruby
117
+ class MyReporter
118
+ def report(title, created_records)
119
+ title # => "controller#action"
120
+ created_records # => [#<ColumnsTrace::CreatedRecord>]
121
+ created_records.each do |record|
122
+ record.model # class of ActiveRecord model
123
+ record.accessed_fields # array of accessed fields
124
+ record.unused_fields # array of unused fields
125
+ record.backtrace # array of strings
126
+ record.record # ActiveRecord model instance
127
+ end
128
+ end
129
+ end
130
+
131
+ ColumnsTrace.reporter = MyReporter.new
132
+ ```
133
+
97
134
  ## Development
98
135
 
99
136
  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
@@ -14,12 +14,20 @@ module ColumnsTrace
14
14
  end
15
15
 
16
16
  ActiveSupport.on_load(:action_controller) do
17
- before_action { Registry.clear }
18
- after_action { Reporter.report("#{self.class.name}##{action_name}") }
17
+ around_action do |controller, action|
18
+ ColumnsTrace.report("#{controller.class.name}##{action_name}", &action)
19
+ end
20
+ end
21
+
22
+ ActiveSupport.on_load(:action_mailer) do
23
+ around_action do |mailer, action|
24
+ ColumnsTrace.report("#{mailer.class.name}##{action_name}", &action)
25
+ end
19
26
  end
20
27
 
21
28
  ActiveSupport.on_load(:active_job) do
22
- before_perform { Registry.clear }
23
- after_perform { Reporter.report(self.class.name) }
29
+ around_perform do |job, perform|
30
+ ColumnsTrace.report("#{job.class.name}#perform", &perform)
31
+ end
24
32
  end
25
33
  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
 
@@ -3,10 +3,8 @@
3
3
  module ColumnsTrace
4
4
  # @private
5
5
  class SidekiqMiddleware
6
- def call(worker, _job, _queue)
7
- Registry.clear
8
- yield
9
- Reporter.report(worker.class.name)
6
+ def call(worker, _job, _queue, &block)
7
+ ColumnsTrace.report("#{worker.class.name}#perform", &block)
10
8
  end
11
9
  end
12
10
  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.3.0"
5
5
  end
data/lib/columns_trace.rb CHANGED
@@ -2,14 +2,32 @@
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
 
11
12
  module ColumnsTrace
12
13
  class << self
14
+ # Manually trace columns usage in an arbitrary code.
15
+ #
16
+ # @param title [String] title of the reporting, e.g. controller action etc
17
+ #
18
+ # @example
19
+ # task my_rake_task: :environment do
20
+ # ColumnsTrace.report("my_rake_task") do
21
+ # # do stuff
22
+ # end
23
+ # end
24
+ #
25
+ def report(title)
26
+ Registry.clear
27
+ yield
28
+ reporter.report(title, Registry.created_records)
29
+ end
30
+
13
31
  # @private
14
32
  attr_reader :ignored_models
15
33
 
@@ -54,11 +72,11 @@ module ColumnsTrace
54
72
  end
55
73
  end
56
74
 
57
- # Allows to set the logger.
58
- # Defaults to logger, that outputs to `log/columns_trace.log` file
75
+ # Allows to set the reporter.
76
+ # Defaults to log reporter that outputs to `log/columns_trace.log` file
59
77
  # when inside a rails application.
60
78
  #
61
- attr_accessor :logger
79
+ attr_accessor :reporter
62
80
 
63
81
  # @private
64
82
  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.3.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-11-08 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