columns_trace 0.1.0 → 0.3.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: 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