cruft_tracker 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +460 -0
- data/Rakefile +8 -0
- data/app/assets/config/cruft_tracker_manifest.js +1 -0
- data/app/assets/stylesheets/cruft_tracker/application.css +15 -0
- data/app/controllers/cruft_tracker/application_controller.rb +4 -0
- data/app/controllers/cruft_tracker/methods_controller.rb +10 -0
- data/app/helpers/cruft_tracker/application_helper.rb +4 -0
- data/app/models/cruft_tracker/application_record.rb +5 -0
- data/app/models/cruft_tracker/argument.rb +7 -0
- data/app/models/cruft_tracker/backtrace.rb +7 -0
- data/app/models/cruft_tracker/method.rb +43 -0
- data/app/services/cruft_tracker/application_service.rb +6 -0
- data/app/services/cruft_tracker/cleanup_untracked_methods.rb +20 -0
- data/app/services/cruft_tracker/record_arguments.rb +41 -0
- data/app/services/cruft_tracker/record_backtrace.rb +53 -0
- data/app/services/cruft_tracker/record_invocation.rb +15 -0
- data/app/services/cruft_tracker/track_all_methods.rb +47 -0
- data/app/services/cruft_tracker/track_method.rb +139 -0
- data/app/views/cruft_tracker/methods/index.html.erb +1 -0
- data/app/views/layouts/cruft_tracker/application.html.erb +15 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20220414134857_create_cruft_tracker_methods.rb +17 -0
- data/db/migrate/20220418133030_create_cruft_tracker_backtraces.rb +20 -0
- data/db/migrate/20220419171326_add_comment_to_cruft_tracker_methods.rb +9 -0
- data/db/migrate/20220419174055_create_cruft_tracker_arguments.rb +15 -0
- data/lib/cruft_tracker/engine.rb +17 -0
- data/lib/cruft_tracker/registry.rb +25 -0
- data/lib/cruft_tracker/version.rb +3 -0
- data/lib/cruft_tracker.rb +31 -0
- data/lib/tasks/cruft_tracker_tasks.rake +4 -0
- 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 @@
|
|
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,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,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
|