cruft_tracker 0.1.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.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +460 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/cruft_tracker_manifest.js +1 -0
  6. data/app/assets/stylesheets/cruft_tracker/application.css +15 -0
  7. data/app/controllers/cruft_tracker/application_controller.rb +4 -0
  8. data/app/controllers/cruft_tracker/methods_controller.rb +10 -0
  9. data/app/helpers/cruft_tracker/application_helper.rb +4 -0
  10. data/app/models/cruft_tracker/application_record.rb +5 -0
  11. data/app/models/cruft_tracker/argument.rb +7 -0
  12. data/app/models/cruft_tracker/backtrace.rb +7 -0
  13. data/app/models/cruft_tracker/method.rb +43 -0
  14. data/app/services/cruft_tracker/application_service.rb +6 -0
  15. data/app/services/cruft_tracker/cleanup_untracked_methods.rb +20 -0
  16. data/app/services/cruft_tracker/record_arguments.rb +41 -0
  17. data/app/services/cruft_tracker/record_backtrace.rb +53 -0
  18. data/app/services/cruft_tracker/record_invocation.rb +15 -0
  19. data/app/services/cruft_tracker/track_all_methods.rb +47 -0
  20. data/app/services/cruft_tracker/track_method.rb +139 -0
  21. data/app/views/cruft_tracker/methods/index.html.erb +1 -0
  22. data/app/views/layouts/cruft_tracker/application.html.erb +15 -0
  23. data/config/routes.rb +3 -0
  24. data/db/migrate/20220414134857_create_cruft_tracker_methods.rb +17 -0
  25. data/db/migrate/20220418133030_create_cruft_tracker_backtraces.rb +20 -0
  26. data/db/migrate/20220419171326_add_comment_to_cruft_tracker_methods.rb +9 -0
  27. data/db/migrate/20220419174055_create_cruft_tracker_arguments.rb +15 -0
  28. data/lib/cruft_tracker/engine.rb +17 -0
  29. data/lib/cruft_tracker/registry.rb +25 -0
  30. data/lib/cruft_tracker/version.rb +3 -0
  31. data/lib/cruft_tracker.rb +31 -0
  32. data/lib/tasks/cruft_tracker_tasks.rake +4 -0
  33. metadata +309 -0
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class RecordArguments < CruftTracker::ApplicationService
5
+ record :method, class: CruftTracker::Method
6
+ array :arguments
7
+ object :transformer, class: Proc
8
+
9
+ private
10
+
11
+ def execute
12
+ arguments_record.with_lock do
13
+ arguments_record.reload
14
+ arguments_record.update(occurrences: arguments_record.occurrences + 1)
15
+ end
16
+
17
+ arguments_record
18
+ end
19
+
20
+ def arguments_record
21
+ @arguments_record ||=
22
+ begin
23
+ CruftTracker::Argument.create(
24
+ method: method,
25
+ arguments_hash: arguments_hash,
26
+ arguments: transformed_arguments
27
+ )
28
+ rescue ActiveRecord::RecordNotUnique
29
+ CruftTracker::Argument.find_by(arguments_hash: arguments_hash)
30
+ end
31
+ end
32
+
33
+ def arguments_hash
34
+ Digest::MD5.hexdigest(transformed_arguments.to_json)
35
+ end
36
+
37
+ def transformed_arguments
38
+ @transformed_arguments ||= transformer.call(arguments)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class RecordBacktrace < CruftTracker::ApplicationService
5
+ record :method, class: CruftTracker::Method
6
+
7
+ private
8
+
9
+ def execute
10
+ backtrace_record.with_lock do
11
+ backtrace_record.reload
12
+ backtrace_record.update(occurrences: backtrace_record.occurrences + 1)
13
+ end
14
+
15
+ backtrace_record
16
+ end
17
+
18
+ def backtrace_record
19
+ @backtrace_record ||=
20
+ begin
21
+ CruftTracker::Backtrace.create(
22
+ traceable: method,
23
+ trace_hash: backtrace_hash,
24
+ trace: filtered_backtrace
25
+ )
26
+ rescue ActiveRecord::RecordNotUnique
27
+ CruftTracker::Backtrace.find_by(trace_hash: backtrace_hash)
28
+ end
29
+ end
30
+
31
+ def backtrace_hash
32
+ Digest::MD5.hexdigest(filtered_backtrace.to_json)
33
+ end
34
+
35
+ def filtered_backtrace
36
+ last_locations_before_tracking_starts =
37
+ caller_locations.reverse.find_index do |location|
38
+ location.path.match(/.*track_method.*/)
39
+ end
40
+
41
+ caller_locations
42
+ .last(last_locations_before_tracking_starts || 0)
43
+ .map do |location|
44
+ {
45
+ path: location.path,
46
+ label: location.label,
47
+ base_label: location.base_label,
48
+ lineno: location.lineno
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class RecordInvocation < CruftTracker::ApplicationService
5
+ record :method, class: CruftTracker::Method
6
+
7
+ def execute
8
+ increment_invocations
9
+ end
10
+
11
+ def increment_invocations
12
+ method.update(invocations: method.reload.invocations + 1)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class TrackAllMethods < CruftTracker::ApplicationService
5
+ object :owner, class: Object
6
+ object :comment, class: Object, default: nil
7
+
8
+ private
9
+
10
+ def execute
11
+ method_records = []
12
+ method_records +=
13
+ own_instance_methods.map do |instance_method|
14
+ CruftTracker::TrackMethod.run!(
15
+ owner: owner,
16
+ name: instance_method,
17
+ method_type: CruftTracker::Method::INSTANCE_METHOD,
18
+ comment: comment
19
+ )
20
+ end
21
+ method_records +=
22
+ own_class_methods.map do |class_method|
23
+ CruftTracker::TrackMethod.run!(
24
+ owner: owner,
25
+ name: class_method,
26
+ method_type: CruftTracker::Method::CLASS_METHOD,
27
+ comment: comment
28
+ )
29
+ end
30
+
31
+ method_records.flatten
32
+ end
33
+
34
+ def own_instance_methods
35
+ owner.instance_methods(false) + owner.private_instance_methods(false)
36
+ end
37
+
38
+ def own_class_methods
39
+ owner.methods(false) +
40
+ owner
41
+ .private_methods(false)
42
+ .select do |method|
43
+ owner.method(method).owner.inspect === owner.singleton_class.inspect
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'cruft_tracker/record_invocation'
4
+ # require 'cruft_tracker/record_backtrace'
5
+ module CruftTracker
6
+ class TrackMethod < CruftTracker::ApplicationService
7
+ private
8
+
9
+ object :owner, class: Module
10
+ symbol :name
11
+ symbol :method_type, default: -> { determine_method_type }
12
+ object :comment, class: Object, default: nil
13
+ object :arguments_transformer, class: Proc, default: nil
14
+
15
+ def execute
16
+ method_record = create_or_find_method_record
17
+ method_record.deleted_at = nil
18
+ method_record.comment = comment if comment != method_record.comment
19
+ method_record.save
20
+
21
+ wrap_target_method(
22
+ method_type,
23
+ target_method,
24
+ method_record,
25
+ arguments_transformer
26
+ )
27
+
28
+ CruftTracker::Registry << method_record
29
+
30
+ method_record
31
+ rescue ActiveRecord::StatementInvalid => e
32
+ raise unless e.cause.present? && e.cause.instance_of?(Mysql2::Error)
33
+
34
+ Rails.logger.warn(
35
+ 'CruftTracker was unable to record a method. Does the cruft_tracker_methods table exist? Have migrations been run?'
36
+ )
37
+ rescue NoMethodError
38
+ Rails.logger.warn(
39
+ 'CruftTracker was unable to record a method. Have migrations been run?'
40
+ )
41
+ rescue Mysql2::Error::ConnectionError,
42
+ ActiveRecord::ConnectionNotEstablished
43
+ Rails.logger.warn(
44
+ 'CruftTracker was unable to record a method due to being unable to connect to the database. This may be a non-issue in cases where the database is intentionally not available.'
45
+ )
46
+ end
47
+
48
+ def wrap_target_method(
49
+ method_type,
50
+ target_method,
51
+ method_record,
52
+ arguments_transformer
53
+ )
54
+ target_method.owner.define_method target_method.name do |*args|
55
+ CruftTracker::RecordInvocation.run!(method: method_record)
56
+ CruftTracker::RecordBacktrace.run!(method: method_record)
57
+ if arguments_transformer.present?
58
+ CruftTracker::RecordArguments.run!(
59
+ method: method_record,
60
+ arguments: args,
61
+ transformer: arguments_transformer
62
+ )
63
+ end
64
+ if method_type == CruftTracker::Method::INSTANCE_METHOD
65
+ target_method.bind(self).call(*args)
66
+ else
67
+ target_method.call(*args)
68
+ end
69
+ end
70
+ end
71
+
72
+ def create_or_find_method_record
73
+ CruftTracker::Method.create(
74
+ owner: owner.name,
75
+ name: name,
76
+ method_type: method_type,
77
+ comment: comment
78
+ )
79
+ rescue ActiveRecord::RecordNotUnique
80
+ CruftTracker::Method.find_by(
81
+ owner: owner.name,
82
+ name: name,
83
+ method_type: method_type
84
+ )
85
+ end
86
+
87
+ def target_method
88
+ case method_type
89
+ when CruftTracker::Method::INSTANCE_METHOD
90
+ owner.instance_method(name)
91
+ when CruftTracker::Method::CLASS_METHOD
92
+ owner.method(name)
93
+ end
94
+ end
95
+
96
+ def determine_method_type
97
+ is_instance_method = all_instance_methods.include?(name)
98
+ is_class_method = all_class_methods.include?(name)
99
+
100
+ if is_instance_method && is_class_method
101
+ raise AmbiguousMethodType.new(owner.name, name)
102
+ elsif is_instance_method
103
+ CruftTracker::Method::INSTANCE_METHOD
104
+ elsif is_class_method
105
+ CruftTracker::Method::CLASS_METHOD
106
+ else
107
+ raise NoSuchMethod.new(owner.name, name)
108
+ end
109
+ end
110
+
111
+ def all_instance_methods
112
+ owner.instance_methods + owner.private_instance_methods
113
+ end
114
+
115
+ def all_class_methods
116
+ owner.methods + owner.private_methods
117
+ end
118
+
119
+ class AmbiguousMethodType < StandardError
120
+ def initialize(owner_name, ambiguous_name)
121
+ super(
122
+ "#{owner_name} has instance and class methods named '#{
123
+ ambiguous_name
124
+ }'. Please specify the correct type."
125
+ )
126
+ end
127
+ end
128
+
129
+ class NoSuchMethod < StandardError
130
+ def initialize(owner_name, missing_name)
131
+ super(
132
+ "#{owner_name} does not have an instance or class method named '#{
133
+ missing_name
134
+ }'."
135
+ )
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Cruft tracker</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "cruft_tracker/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ CruftTracker::Engine.routes.draw do
2
+ resources :methods, only: %i[index]
3
+ end
@@ -0,0 +1,17 @@
1
+ class CreateCruftTrackerMethods < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :cruft_tracker_methods do |t|
4
+ t.string :owner, null: false
5
+ t.string :name, null: false
6
+ t.string :method_type, null: false
7
+ t.integer :invocations, null: false, default: 0
8
+ t.datetime :deleted_at
9
+ t.timestamps
10
+
11
+ t.index :owner
12
+ t.index :name
13
+ t.index %i[owner name]
14
+ t.index %i[owner name method_type], unique: true
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ class CreateCruftTrackerBacktraces < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :cruft_tracker_backtraces do |t|
4
+ t.references :traceable,
5
+ polymorphic: true,
6
+ null: false,
7
+ index: {
8
+ name: 'index_pcbt_on_traceable_id_and_type'
9
+ }
10
+ t.string :trace_hash, null: false, index: true
11
+ t.json :trace, null: false
12
+ t.integer :occurrences, null: false, index: true, default: 0
13
+ t.timestamps
14
+
15
+ t.index %i[traceable_id trace_hash],
16
+ unique: true,
17
+ name: 'index_pcbt_on_traceable_id_and_trace_hash'
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ class AddCommentToCruftTrackerMethods < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_column :cruft_tracker_methods,
4
+ :comment,
5
+ :json,
6
+ null: true,
7
+ after: :invocations
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ class CreateCruftTrackerArguments < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :cruft_tracker_arguments do |t|
4
+ t.references :method, null: false
5
+ t.string :arguments_hash, null: false, index: true
6
+ t.json :arguments, null: false
7
+ t.integer :occurrences, null: false, index: true, default: 0
8
+ t.timestamps
9
+
10
+ t.index %i[method_id arguments_hash],
11
+ unique: true,
12
+ name: 'index_pca_on_method_id_and_arguments_hash'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module CruftTracker
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace CruftTracker
4
+
5
+ config.after_initialize do
6
+ CruftTracker::CleanupUntrackedMethods.run!
7
+ rescue StandardError
8
+ # Swallow all errors to prevent initialization failures.
9
+ end
10
+
11
+ # initializer 'cruft_tracker.action_controller' do |app|
12
+ # ActiveSupport.on_load(:action_controller) do
13
+ # ::ActionController::Base.helper(CruftTracker::ApplicationHelper)
14
+ # end
15
+ # end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class Registry
5
+ include Singleton
6
+
7
+ attr_accessor :tracked_methods
8
+
9
+ def self.<<(tracked_method)
10
+ instance.tracked_methods << tracked_method
11
+ end
12
+
13
+ def self.include?(tracked_method)
14
+ instance.tracked_methods.include?(tracked_method)
15
+ end
16
+
17
+ def self.reset
18
+ instance.tracked_methods = []
19
+ end
20
+
21
+ def initialize
22
+ @tracked_methods = []
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module CruftTracker
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,31 @@
1
+ require 'cruft_tracker/version'
2
+ require 'cruft_tracker/engine'
3
+ require 'cruft_tracker/registry'
4
+
5
+ module CruftTracker
6
+ # Your code goes here...
7
+
8
+ def self.is_this_view_used?
9
+ puts '>>>> is this view used?'
10
+ end
11
+
12
+ def self.is_this_method_used?(
13
+ owner,
14
+ name,
15
+ method_type: nil,
16
+ comment: nil,
17
+ track_arguments: nil
18
+ )
19
+ CruftTracker::TrackMethod.run!(
20
+ owner: owner,
21
+ name: name,
22
+ method_type: method_type,
23
+ comment: comment,
24
+ arguments_transformer: track_arguments
25
+ )
26
+ end
27
+
28
+ def self.are_any_of_these_methods_being_used?(owner, comment: nil)
29
+ CruftTracker::TrackAllMethods.run!(owner: owner, comment: comment)
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :cruft_tracker do
3
+ # # Task goes here
4
+ # end