cruft_tracker 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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