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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6872c1981b26d42c76dc4193a91b40aa05c931f4ee24762db71b17b2ab37d121
4
+ data.tar.gz: 96e8425050bd7f65621bb3b3353dc5faa8f543369c863d1b6ed593c55313263d
5
+ SHA512:
6
+ metadata.gz: dbcb404cc15715242373e8cf6ac4f973cfdaede0ca3ec6a8dc4379515a153e79aea612335d1b2ad87c7773e732cb79b816a5d9641dbf2e3ea63bd023323f7632
7
+ data.tar.gz: dbefa67a93ebdec9f702ba28bc3b303550c61df3f34dd1bc1d07a8a6bd13e4e5a9ac4c6efe23bd68690153b3ea4a03c028300928368bda246bccfabb9080fb71
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022 Adwerx, Inc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,460 @@
1
+ # CruftTracker
2
+
3
+ Have you ever asked yourself, "Is this method even being used?!" Or, "What the heck is this method receiving?" Does your application use Rails? If the answers
4
+ these questions are yes, this gem may be of use to you!
5
+
6
+ Large applications can accrue cruft; old methods that might once have been important, but are now unused or code that is difficult to understand, but dangerous to refactor. Unfortunately,
7
+ software is _complex_ and sometimes it's unclear what's really going on. This adds maintenance burdens, headaches, and uncertainty.
8
+
9
+ This gem aims to give you a couple tools to make it easier to know what (and how) your code is being used (or not).
10
+
11
+ CruftTracker supports Rails versions 5.2 to 6.1 at this time. As of now the gem only supports MySQL, but contributions for Postgres other DBMS would be welcome.
12
+
13
+ ## Installation
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'cruft_tracker'
18
+ ```
19
+
20
+ And then execute:
21
+ ```bash
22
+ bundle
23
+ ```
24
+
25
+ Or install it yourself as:
26
+ ```bash
27
+ gem install cruft_tracker
28
+ ```
29
+
30
+ You'll need to create the migrations to add the required tables to your database:
31
+
32
+ ```bash
33
+ bin/rails cruft_tracker:install:migrations
34
+ ```
35
+
36
+ After that, you can run migrations as you normally would. If you've previously installed this gem and are updating it, you may need to install any new or updated migrations. Just run the command above again when you upgrade to ensure you get the latest migrations.
37
+
38
+ ## Usage
39
+
40
+ ### Tracking method invocations
41
+
42
+ CruftTracker is pretty simple. Let's say you have a class (or module) like this...
43
+
44
+ ```ruby
45
+ class SomeOldClass
46
+ def some_old_method
47
+ # do things
48
+ end
49
+ end
50
+ ```
51
+
52
+ You're unsure if the `some_old_method` method is actually being used. All you need to do is use `CruftTracker.is_this_method_used?`. This method requires you to pass `self` and a symbol to identify the name of the method to track. For example:
53
+
54
+ ```ruby
55
+ class SomeOldClass
56
+ def some_old_method
57
+ # do things
58
+ end
59
+
60
+ CruftTracker.is_this_method_used? self, :some_old_method
61
+ end
62
+ ```
63
+
64
+ What do you get out of this? Well, as soon as Ruby loads the `SomeOldClass` class, CruftTracker will create a new record
65
+ in the `cruft_tracker_methods` table that looks like this:
66
+
67
+
68
+ | id | owner | name | method_type | invocations | comment | deleted_at | created_at | updated_at |
69
+ | --- | ------------ | --------------- | --------------- | ----------- | ------- | ---------- | ------------------- | ------------------- |
70
+ | 1 | SomeOldClass | some_old_method | instance_method | 0 | null | null | 2022-01-21 14:07:48 | 2022-01-21 14:07:48 |
71
+
72
+ This record is accessible using the `CruftTracker::Method` model. EG: `CruftTracker::Method.find(1)`
73
+
74
+ The fields are:
75
+
76
+ - `id` - Shockingly, this is the primary key.
77
+ - `owner` - This is the name of the Ruby class or module that owns the method.
78
+ - `name` - This is the name of the method.
79
+ - `method_type` - This is either "instance_method" or "class_method", which are the values of the corresponding
80
+ constants, `CruftTracker::Method::INSTANCE_METHOD` and `CruftTracker::Method::CLASS_METHOD`.
81
+ - `invocations` - The number of times the method has been invoked.
82
+ - `comments` - This is a JSON field containing extra data provided to the option `comments:` argument for the `is_this_method_used?` method.
83
+ - `deleted_at` - When set, this indicates that the method is no longer being tracked.
84
+ - `created_at` - The date/time we started tracking the method.
85
+ - `updated_at` - The last time this record was updated. IE: the last time the tracked method was invoked.
86
+
87
+ Looking at this, we can see that the `some_old_method` method has never been invoked. This is nice because it means that
88
+ you can track uses of methods without changing their behavior and also know when a method has _not_ been used. A similar record is created for every method you annotate
89
+ with `CruftTracker.is_this_method_used?`.
90
+
91
+ Assuming your production application eagerly loads classes, you should always have records for potentially crufty
92
+ methods, even if the class itself is never explicitly used.
93
+
94
+ So, having annotated the method, you can check this table after a while. If you see that there have been zero invocations,
95
+ you have a reasonably good hint that the method may not actually be used. Of course, you should consider that there are
96
+ some processes that are not run frequently at all, so this gem isn't a panacea. **Think before you delete!**
97
+
98
+ `CruftTracker.is_this_method_used?` can be used to track any kind of method (except `initialize`) with any visibility. This includes class and module methods (`self.`), private class methods, eigenclass methods, as well as public, private, and protected instance methods.
99
+
100
+ ### Comments
101
+
102
+ Since you may have to track a method for a while, it might be helpful to have a reminder as to _why_ you're tracking it in the first place. This can be recorded by providing an optional `comments:` named argument. For example:
103
+
104
+ ```ruby
105
+ class SomeOldClass
106
+ def some_old_method
107
+ # do things
108
+ end
109
+
110
+ CruftTracker.is_this_method_used?
111
+ self,
112
+ :some_old_method,
113
+ comment: "I suspect this method is being called via metaprogramming."
114
+ end
115
+ ```
116
+
117
+ `comment:` can be anything that can be serialized to JSON. For example:
118
+
119
+ ```ruby
120
+ class SomeOldClass
121
+ def some_old_method
122
+ # do things
123
+ end
124
+
125
+ CruftTracker.is_this_method_used?
126
+ self,
127
+ :some_old_method,
128
+ comment: {
129
+ creator: "Doug Hughes",
130
+ note: "Found while working on marketing code. I suspect this method is unused."
131
+ }
132
+ end
133
+ ```
134
+
135
+ The comment is serialized to json and stored in the `comments` field of the `CruftTracker::Method` record.
136
+
137
+ ### Tracking Backtraces / Stacktraces
138
+
139
+ By default, CruftTracker will record unique backtraces for each invocation of a tracked method. This data is stored in the `cruft_tracker_backtraces` table and is accessible via the `CruftTracker::Method`'s `backtraces` association. The `cruft_tracker_backtraces` table has the following columns:
140
+
141
+ - `id` - Ye olde primary key.
142
+ - `traceable_type` - The type for the polymorphic `traceable` association. Future versions of CruftTracker may track data in addition to method invocations.
143
+ - `traceable_id` - The ID of the polymorphic `traceable` association. EG: the `CruftTracker::Method` the backtrace is recorded for.
144
+ - `trace_hash` - Traces are stored as JSON. This column is an MD5 hash of the trace that is indexed to make it easier / faster to know if we've seen a particular trace before.
145
+ - `trace` - The trace data, stored as a JSON array of hashes.
146
+ - `occurrences` - This is the number of times we've seen a particular backtrace.
147
+ - `created_at` - The first time we saw a particular backtrace.
148
+ - `updated_at` - The most recent time we saw a particular backtrace.
149
+
150
+ Backtraces can be referenced to figure out exactly where a tracked method is being used. It also implicitly tells you other code that is definitely being used. Do note that as code changes, these record backtraces are not updated. So, if a backtrace says the tracked method was invoked from line 123 of some file, if that file is edited, the line numbers may no longer match. Also, this would be record as a new backtrace.
151
+
152
+ Future versions of CruftTracker may provide a UI for exploring backtraces.
153
+
154
+ ### Tracking Arguments
155
+
156
+ You can optionally track details about arguments provided to tracked methods. This is done via a proc passed to the the `CruftTracker::Method`'s optional `track_arguments:` argument. For example, let's say you have the following method and that it has no test coverage:
157
+
158
+ ```ruby
159
+ class SomeClass
160
+ def do_something_via_metaprogramming(options)
161
+ options[:target_class].constantize.send(options[:method], options[:modifiers])
162
+ YetAnotherClass.do_something_else(options)
163
+ end
164
+ end
165
+ ```
166
+
167
+ Take a moment and read that glorious mess. When you're done feeling queasy, read on:
168
+
169
+ Assuming `do_something_via_metaprogramming` is being used at all, we know:
170
+
171
+ - It calls an arbitrary method (specified via `options[:method]`) on an arbitrary class (specified via `options[:target_class]`) and passes an unknown argument to it (`options[:modifiers]`).
172
+ - It passes `options` to `YetAnotherClass.do_something_else`.
173
+ - `options` is _probably_ a hash.
174
+
175
+ Here's what we don't know:
176
+
177
+ - We have no idea what classes might receive whatever method is being invoked.
178
+ - We don't know what method is being invoked.
179
+ - We don't know what's being passed to that method.
180
+ - We don't know anything about the structure of `options` at all, so we don't know what's being passed to `YetAnotherClass.do_something_else`.
181
+
182
+ Now, I ask you a few questions:
183
+
184
+ 1. Can we safely delete `SomeClass#do_something_via_metaprogramming`?
185
+ 2. What options does `do_something_via_metaprogramming` receive? Are they always the same options?
186
+ 3. What classes and methods does `do_something_via_metaprogramming` invoke via metaprogramming?
187
+
188
+ The answer: _Who the heck knows?!_ 🤷
189
+
190
+ So, let's start collecting some data about these arguments. We can do this with the `track_arguments:` named argument on `is_this_method_used?`. This argument takes a proc that receives an `args` array as an argument. Whatever the proc returns is serialized to JSON and stored in the `cruft_tracker_arguments` table.
191
+
192
+ The naive approach to tracking arguments would be to use something like this:
193
+
194
+ ```ruby
195
+ class SomeClass
196
+ def do_something_via_metaprogramming(options)
197
+ options[:target_class].constantize.send(options[:method], options[:modifiers])
198
+ YetAnotherClass.do_something_else(options)
199
+ end
200
+
201
+ CruftTracker.is_this_method_used?
202
+ self,
203
+ :do_something_via_metaprogramming,
204
+ track_arguments: -> (args) { args }
205
+ end
206
+ ```
207
+
208
+ This will track all of the values of the options provided to the `do_something_via_metaprogramming` method. This could be a problem. Consider a case where the method is used a zillion times per day and where there's a plethora of complicated data being passed through the method via its `options` argument. It's possible that each set of arguments is different. This could result in one `potential_cruft_arguments` record per invocation of the tracked method. This is both probably not what. What you probably want to know in this case is:
209
+
210
+ - What are the unique sets of keys in the `options` hash?
211
+ - What classes and methods are we calling via metaprogramming?
212
+
213
+ We could write a proc that looks like this:
214
+
215
+ ```ruby
216
+ class SomeClass
217
+ def do_something_via_metaprogramming(options)
218
+ options[:target_class].constantize.send(options[:method], options[:modifiers])
219
+ YetAnotherClass.do_something_else(options)
220
+ end
221
+
222
+ CruftTracker.is_this_method_used?
223
+ self,
224
+ :do_something_via_metaprogramming,
225
+ track_arguments: -> (args) do
226
+ options = args.first
227
+
228
+ {
229
+ options_keys: options.keys.sort,
230
+ metaprogramming_target: "#{options[:target_class]}##{options[:method]}"
231
+ }
232
+ end
233
+ end
234
+ ```
235
+
236
+ So, let's say the method is invoked like this:
237
+
238
+ ```ruby
239
+ SomeClass.new.do_something_via_metaprogramming(
240
+ target_class: 'PigLatinTranslator',
241
+ method: 'translate',
242
+ modifiers: {
243
+ change_case: true,
244
+ reverse: false,
245
+ other_data: [:a, "foo", {x: 123}]
246
+ },
247
+ title: 'blargh'
248
+ )
249
+
250
+ SomeClass.new.do_something_via_metaprogramming(
251
+ method: 'send_email',
252
+ target_class: 'MarketingMailer',
253
+ modifiers: {
254
+ recipient: 'foo@bar.com',
255
+ subject: "We'd like to talk to you about your car's warranty.",
256
+ distributor: 'XYZ'
257
+ },
258
+ warranty_data: {
259
+ fake: true,
260
+ lunch_plans: 'Panda Pavilion'
261
+ },
262
+ whatever: 42
263
+ )
264
+
265
+ SomeClass.new.do_something_via_metaprogramming(
266
+ method: 'translate',
267
+ target_class: 'PigLatinTranslator',
268
+ title: 'Old Man and the Sea',
269
+ modifiers: {
270
+ change_case: false,
271
+ other_data: "whatever"
272
+ }
273
+ )
274
+ ```
275
+
276
+ With the naive approach, we'd have logged three arguments with a ton of data that may or may not be useful. With the second example's `track_arguments:` proc, we'd end up with two records containing this information:
277
+
278
+ The first record's arguments:
279
+
280
+ ```json
281
+ {
282
+ "options_keys": ["method","modifiers","target_class","title"],
283
+ "metaprogramming_target": "PigLatinTranslator#translate"
284
+ }
285
+ ```
286
+
287
+ The second record's arguments:
288
+
289
+ ```json
290
+ {
291
+ "options_keys": ["method","modifiers","target_class","warranty_data","whatever"],
292
+ "metaprogramming_target": "MarketingMailer#send_email"
293
+ }
294
+ ```
295
+
296
+ Using this approach, you can start to update the `do_something_via_metaprogramming` method to, perhaps, explicitly name the options it accepts, replace metaprogramming with explicit code to make it's clear what classes and methods are being invoked, etc.
297
+
298
+ Arguments are tracked in the `cruft_tracker_arguments` table which has these columns:
299
+
300
+ - `id` - An ID. (I bet you did't see that coming!)
301
+ - `method_id` - The ID of the method the argument record belongs to.
302
+ - `arguments_hash` - Arguments are stored as JSON. This column is an MD5 hash of the arguments that is indexed to make it easier / faster to know if we've seen a particular set of arguments before.
303
+ - `arguments` - The transformed argument data, stored as JSON data.
304
+ - `occurrences` - This is the number of times we've seen a particular set of arguments.
305
+ - `created_at` - The first time we saw a particular set of arguments.
306
+ - `updated_at` - The most recent time we saw a particular set of arguments.
307
+
308
+
309
+ ### Tracking Everything
310
+
311
+ So, let's say you've got a class with a bunch of methods. You want to know if any of the methods are being used, and you just don't want to think very hard about it. That's where `CruftTracker.are_any_of_these_methods_being_used?` comes to the rescue! Just tack that onto the end of your class like this to track all method invocations:
312
+
313
+ ```ruby
314
+ class SomeClass
315
+
316
+ def self.do_something
317
+ # ...
318
+ end
319
+
320
+ def jump_up_and_down
321
+ # ...
322
+ end
323
+
324
+ private
325
+
326
+ def say_hi(to)
327
+ # ...
328
+ end
329
+
330
+ # ... other methods ...
331
+
332
+ CruftTracker.are_any_of_these_methods_being_used? self
333
+ end
334
+ ```
335
+
336
+ This will result in a `cruft_tracker_methods` record being created for each method in the `SomeClass` class. It will _not_ track methods that exist in the class' (or module's) ancestors. It's a quick and easy way to see what's being used. This method cannot be used to track arguments, though it does accept a `comments:` named argument.
337
+
338
+ You may want to think twice about using this method, or using this method too widely as it may create more data than you expect. CruftTracker is lightweight, but too much of a good thing is still too much. Generally, you should favor being targeted in your tracking.
339
+
340
+ ### Clean Up
341
+
342
+ CruftTacker automatically cleans up after itself. ✨🧹 If you remove an instance of `CruftTracker.is_this_method_used?` to stop tracking a method, CruftTracker will recognize this when your application starts up and mark the associated `cruft_tracker_methods` record as deleted. But, only in environments where eager loading is enabled.
343
+
344
+ ## API Docs
345
+
346
+ ### `CruftTracker` module methods
347
+
348
+ #### `#is_this_method_used?`
349
+
350
+ Used to indicate that a particular method should be tracked. Creates a record in the `cruft_tracker_methods` table.
351
+
352
+ Returns an instance of `CruftTracker::Method`.
353
+
354
+ ##### Arguments
355
+
356
+ | Name | Type | Required? | Default | Description |
357
+ | ------------------------ | --------------------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
358
+ | owner (positional) | a class or module constant | yes | N/A | A reference to the class or module that owns the method. Set this to `self`. |
359
+ | name (positional) | symbol | yes | N/A | The name of the method to track in symbol form. Do not include `self.` for class methods, just the name of the method. EG: `:some_method_to_track`. |
360
+ | method_type: (named) | string | no | nil | Used to disambiguate between class and instance methods with the same name. Must be either `CruftTracker::Method::INSTANCE_METHOD` or `CruftTracker::Method::CLASS_METHOD`. |
361
+ | comment: (named) | anything that can be serialized to json | no | nil | Arbitrary data you want to include with the `cruft_tracker_methods` record. For example, a note about why the method is being tracked or a hash with keys indicating who is tracking the method and and why. |
362
+ | track_arguments: (named) | a proc | no | nil | A proc that accepts `args` and transforms them for logging. (See [Tracking Arguments](#tracking-arguments).) |
363
+
364
+ #### `#are_any_of_these_methods_being_used?`
365
+
366
+ Used to track all methods belonging to the class tagged with this method. This must be used at the end of the class so that all tracked methods are already defined when the class is loaded.
367
+
368
+ Returns an array of `CruftTracker::Method` instances.
369
+
370
+ ##### Arguments
371
+
372
+ | Name | Type | Required? | Default | Description |
373
+ | ------------------------ | --------------------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
374
+ | owner (positional) | a class or module constant | yes | N/A | A reference to the class or module that owns the method. Set this to `self`. |
375
+ | comment: (named) | anything that can be serialized to json | no | nil | Arbitrary data you want to include with the `cruft_tracker_methods` record. For example, a note about why the method is being tracked or a hash with keys indicating who is tracking the method and and why. |
376
+
377
+ ## Developing
378
+
379
+ A Docker / docker-compose environment is available to simplify development and is recommended. Assuming you already have Docker installed, you can spin up MySQL and open a bash console on a container with Ruby installed like this:
380
+
381
+ ```bash
382
+ docker-compose run --rm ruby bash
383
+ ```
384
+
385
+ The MySQL server has its port exposed as 13306. Note that the first time you spin up these containers it may take a moment for mysql to successfully spin up.
386
+
387
+ The gem's source is mapped to `/app`, which is also the working directory.
388
+
389
+ Once you have a bash console open, you can install dependencies with:
390
+
391
+ ```bash
392
+ bundle install
393
+ bundle exec appraisal install
394
+ ```
395
+
396
+ You can copy the provided MySQL DB config file to be the one to use in the test app:
397
+
398
+ ```bash
399
+ cp spec/dummy_app/config/database.mysql.yml spec/dummy_app/config/database.yml
400
+ ```
401
+
402
+ And now you should be able to run any command against whichever version of Rails you wish (5.2, 6.0, or 6.1), like so:
403
+
404
+ ```bash
405
+ bundle exec appraisal rails-6.1 <some command>
406
+ ```
407
+
408
+ For example:
409
+
410
+ ```bash
411
+ # open a Rails 5.2 console
412
+ bundle exec appraisal rails-5.2 rails c
413
+
414
+ # run a rake task with rails-6.0
415
+ bundle exec appraisal rails-6.0 rake <some task>
416
+
417
+ # run tests
418
+ bundle exec appraisal rails-6.1 rspec spec
419
+ ```
420
+
421
+ ### Running the dummy app
422
+
423
+ You can run the dummy app with docker-compose like so:
424
+
425
+ ```bash
426
+ rm tmp/pids/server.pid
427
+ docker-compose stop ruby
428
+ RAILS_VERSION=6.1 docker-compose up -d ruby
429
+ docker attach cruft_tracker_ruby_1
430
+ ```
431
+
432
+ The `RAILS_VERSION` environment variable is required. Options are: 5.2, 6.0, or 6.1. This will run the application on port 3000 and it can be accessed in your browser at http://localhost:3000. You should be able to use `binding.pry` for debugging.
433
+
434
+ ### Running tests
435
+
436
+ Tests can be run from a Docker bash console like this:
437
+
438
+ ```bash
439
+ bundle exec appraisal rails-5.2 rspec ./spec
440
+ ```
441
+
442
+ ## Building and Publishing the Gem (because I always forget)
443
+
444
+ Be sure the bump the version in `CruftTracker::VERSION` before building the gem to publish.
445
+
446
+ ```bash
447
+ gem build cruft_tracker.gemspec
448
+ ```
449
+
450
+ This will create a new gem file
451
+
452
+ ## Contributing
453
+
454
+ Bug reports and pull requests are welcome on GitHub at https://github.com/AdWerx/cruft-tracker.
455
+
456
+ Contributions should use Prettier to format Ruby code and must have tests covering any new features. All unit tests must pass for all supported versions of Rails.
457
+
458
+ ## License
459
+
460
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/cruft_tracker .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module CruftTracker
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class MethodsController < ApplicationController
5
+
6
+ def index
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ module CruftTracker
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module CruftTracker
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class Argument < ActiveRecord::Base
5
+ belongs_to :method
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class Backtrace < ActiveRecord::Base
5
+ belongs_to :traceable, polymorphic: true
6
+ end
7
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class Method < ActiveRecord::Base
5
+ INSTANCE_METHOD = :instance_method
6
+ CLASS_METHOD = :class_method
7
+
8
+ has_many :backtraces, class_name: 'CruftTracker::Backtrace', as: :traceable
9
+ has_many :arguments, class_name: 'CruftTracker::Argument'
10
+
11
+ def still_exists?
12
+ class_still_exists? && method_still_exists?
13
+ end
14
+
15
+ def still_tracked?
16
+ CruftTracker::Registry.include?(self)
17
+ end
18
+
19
+ def ==(other)
20
+ other.owner == owner && other.name == name &&
21
+ other.method_type == method_type
22
+ end
23
+
24
+ private
25
+
26
+ def class_still_exists?
27
+ Object.const_defined?(owner)
28
+ end
29
+
30
+ def clazz
31
+ owner.constantize
32
+ end
33
+
34
+ def method_still_exists?
35
+ case method_type
36
+ when CruftTracker::Method::INSTANCE_METHOD.to_s
37
+ (clazz.instance_methods + clazz.private_instance_methods)
38
+ when CruftTracker::Method::CLASS_METHOD.to_s
39
+ (clazz.methods + clazz.private_methods)
40
+ end.include?(name.to_sym)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class ApplicationService < ActiveInteraction::Base
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class CleanupUntrackedMethods < CruftTracker::ApplicationService
5
+ private
6
+
7
+ def execute
8
+ CruftTracker::Method
9
+ .where(deleted_at: nil)
10
+ .each do |method|
11
+ unless method.still_exists? && method.still_tracked?
12
+ method.update(deleted_at: Time.current)
13
+ end
14
+ end
15
+ rescue StandardError
16
+ # I'm actively ignoring all errors. Chances are, these are due to something like running rake
17
+ # tasks in CI when the DB doesn't already exist.
18
+ end
19
+ end
20
+ end