cruft_tracker 0.1.4 → 0.2.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: 7dc443c51a9e3e97dce2c669591a34eb840d6ea0e7ea99e2d9fae20410c2a49a
4
- data.tar.gz: 81b3d73761b33e0ce81dc2967686502e2c16d796f12826651221209fadf9a08e
3
+ metadata.gz: 20710aba981f3164c44b99d0aa870650f19aac7a64b31b8b866655a479dae74c
4
+ data.tar.gz: 3a4b8fec7d399446a31aef44d77a8fc35e8affc209370393fc3d6bae9037f95b
5
5
  SHA512:
6
- metadata.gz: 583a4dd4ccb6f97e1c10a9dd3e5e2855ae3898bd97ba20abeadf13fe7e2441b0f24a1a1d4208e14054712812a84bb41db1bc7af5f2ed9d259fbdd175f0736a62
7
- data.tar.gz: f88a8a2c8bd7a2a652f8dad3f416f84f7b430b4373dd0180b69ee6ff5fe4c982b79beb8ba0b1ca5d939ec66b560605020360e6b2d20528f76add7f67f98d525e
6
+ metadata.gz: ad71fc2c540815704899075e337d1c5030165911441befb63d8432689b258e56319c111a3b9dc445eaa67c1e2486dd9bb1af80a82270bfc74c7a523354bf9a5a
7
+ data.tar.gz: a2d602fa9a427d8c457e38379c8e03707a3d8b58fe4497dbaea8b0e1c3493812dae652c8843645cb42deb430798c67fdcd2213f3491afda1625c3284f97951fe
data/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # CruftTracker
2
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
3
+ Have you ever asked yourself, "Is this method even being used?!" Or, "What the heck is this method receiving?" Or, perhaps, "is this partial being used?" Does your application use Rails? If the answers
4
4
  these questions are yes, this gem may be of use to you!
5
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,
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. The same is can be true for views and partials in Rails applications. Unfortunately,
7
7
  software is _complex_ and sometimes it's unclear what's really going on. This adds maintenance burdens, headaches, and uncertainty.
8
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).
9
+ This gem aims to give you some tools to make it easier to know what (and how) your code is being used (or not).
10
10
 
11
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 or other DBMS would be welcome.
12
12
 
@@ -37,6 +37,16 @@ After that, you can run migrations as you normally would. If you've previously i
37
37
 
38
38
  ## Usage
39
39
 
40
+ ### Rails Initializer
41
+
42
+ You should configure CruftTracker by creating an initializer in your Rails application's `config/initializers` directory named `cruft_tracker.rb`. The structure of the initializer is:
43
+
44
+ ```ruby
45
+ CruftTracker.init do
46
+ # your configuration.... (more on this below)
47
+ end
48
+ ```
49
+
40
50
  ### Tracking method invocations
41
51
 
42
52
  CruftTracker is pretty simple. Let's say you have a class (or module) like this...
@@ -49,19 +59,17 @@ class SomeOldClass
49
59
  end
50
60
  ```
51
61
 
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:
62
+ You're unsure if the `some_old_method` method is actually being used. All you need to do is add an instance of the `is_this_method_used? ` to the CruftTracker initializer. This method requires you to pass the class constant and a symbol to identify the name of the method to track. For example:
53
63
 
54
64
  ```ruby
55
- class SomeOldClass
56
- def some_old_method
57
- # do things
58
- end
65
+ # config/initializers/cruft_tracker.rb
59
66
 
60
- CruftTracker.is_this_method_used? self, :some_old_method
67
+ CruftTracker.init do
68
+ is_this_method_used? SomeOldClass, :some_old_method
61
69
  end
62
70
  ```
63
71
 
64
- What do you get out of this? Well, as soon as Ruby loads the `SomeOldClass` class, CruftTracker will create a new record
72
+ What do you get out of this? Well, as soon as Rails runs the CruftTracker initializer, CruftTracker will create a new record
65
73
  in the `cruft_tracker_methods` table that looks like this:
66
74
 
67
75
 
@@ -85,30 +93,28 @@ The fields are:
85
93
  - `updated_at` - The last time this record was updated. IE: the last time the tracked method was invoked.
86
94
 
87
95
  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?`.
96
+ you can track uses of methods without changing their behavior (or editing their class) and also know when a method has _not_ been used. A similar record is created for every method you track
97
+ with `is_this_method_used?`.
90
98
 
91
- Assuming your production application eagerly loads classes, you should always have records for potentially crufty
99
+ You should always have records for potentially crufty
92
100
  methods, even if the class itself is never explicitly used.
93
101
 
94
- So, having annotated the method, you can check this table after a while. If you see that there have been zero invocations,
102
+ So, having configured the method to be tracked, you can check this table after a while. If you see that there have been zero invocations,
95
103
  you have a reasonably good hint that the method may not actually be used. Of course, you should consider that there are
96
104
  some processes that are not run frequently at all, so this gem isn't a panacea. **Think before you delete!**
97
105
 
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.
106
+ `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
107
 
100
108
  ### Comments
101
109
 
102
110
  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
111
 
104
112
  ```ruby
105
- class SomeOldClass
106
- def some_old_method
107
- # do things
108
- end
113
+ # config/initializers/cruft_tracker.rb
109
114
 
110
- CruftTracker.is_this_method_used?
111
- self,
115
+ CruftTracker.init do
116
+ is_this_method_used?
117
+ SomeOldClass,
112
118
  :some_old_method,
113
119
  comment: "I suspect this method is being called via metaprogramming."
114
120
  end
@@ -117,13 +123,11 @@ end
117
123
  `comment:` can be anything that can be serialized to JSON. For example:
118
124
 
119
125
  ```ruby
120
- class SomeOldClass
121
- def some_old_method
122
- # do things
123
- end
126
+ # config/initializers/cruft_tracker.rb
124
127
 
125
- CruftTracker.is_this_method_used?
126
- self,
128
+ CruftTracker.init do
129
+ is_this_method_used?
130
+ SomeOldClass,
127
131
  :some_old_method,
128
132
  comment: {
129
133
  creator: "Doug Hughes",
@@ -139,7 +143,7 @@ The comment is serialized to json and stored in the `comments` field of the `Cru
139
143
  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
144
 
141
145
  - `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.
146
+ - `traceable_type` - The type for the polymorphic `traceable` association. Future versions of CruftTracker may track data in addition to method invocations. (Note: This was originally made polymorphic to support view tracking. Since view tracking can't use the backtraces feature, the polymorphism is vestigial and may be removed in the future.)
143
147
  - `traceable_id` - The ID of the polymorphic `traceable` association. EG: the `CruftTracker::Method` the backtrace is recorded for.
144
148
  - `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
149
  - `trace` - The trace data, stored as a JSON array of hashes.
@@ -147,7 +151,7 @@ By default, CruftTracker will record unique backtraces for each invocation of a
147
151
  - `created_at` - The first time we saw a particular backtrace.
148
152
  - `updated_at` - The most recent time we saw a particular backtrace.
149
153
 
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.
154
+ 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 backtrace records 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 and would be recorded as a new backtrace.
151
155
 
152
156
  Future versions of CruftTracker may provide a UI for exploring backtraces.
153
157
 
@@ -185,42 +189,36 @@ Now, I ask you a few questions:
185
189
  2. What options does `do_something_via_metaprogramming` receive? Are they always the same options?
186
190
  3. What classes and methods does `do_something_via_metaprogramming` invoke via metaprogramming?
187
191
 
188
- The answer: _Who the heck knows?!_ 🤷
192
+ The answers: _Who the heck knows?!_ 🤷
189
193
 
190
194
  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
195
 
192
196
  The naive approach to tracking arguments would be to use something like this:
193
197
 
194
198
  ```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
199
+ # config/initializers/cruft_tracker.rb
200
200
 
201
- CruftTracker.is_this_method_used?
202
- self,
201
+ CruftTracker.init do
202
+ is_this_method_used?
203
+ SomeClass,
203
204
  :do_something_via_metaprogramming,
204
205
  track_arguments: -> (args) { args }
205
206
  end
206
207
  ```
207
208
 
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
+ 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 may not be what you want to know... What you probably want to know in this case is:
209
210
 
210
211
  - What are the unique sets of keys in the `options` hash?
211
212
  - What classes and methods are we calling via metaprogramming?
212
213
 
213
- We could write a proc that looks like this:
214
+ Instead, we could write a proc that looks like this:
214
215
 
215
216
  ```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
217
+ # config/initializers/cruft_tracker.rb
221
218
 
222
- CruftTracker.is_this_method_used?
223
- self,
219
+ CruftTracker.init do
220
+ is_this_method_used?
221
+ SomeClass,
224
222
  :do_something_via_metaprogramming,
225
223
  track_arguments: -> (args) do
226
224
  options = args.first
@@ -306,9 +304,11 @@ Arguments are tracked in the `cruft_tracker_arguments` table which has these col
306
304
  - `updated_at` - The most recent time we saw a particular set of arguments.
307
305
 
308
306
 
309
- ### Tracking Everything
307
+ ### Tracking All Methods
310
308
 
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:
309
+ 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 `are_any_of_these_methods_being_used?` comes to the rescue! Just add this to the CruftTracker initializer as shown below.
310
+
311
+ Let's say this is your class:
312
312
 
313
313
  ```ruby
314
314
  class SomeClass
@@ -328,18 +328,208 @@ class SomeClass
328
328
  end
329
329
 
330
330
  # ... other methods ...
331
+ end
332
+ ```
333
+
334
+ Here's the configuration in the initializer:
331
335
 
332
- CruftTracker.are_any_of_these_methods_being_used? self
336
+ ```ruby
337
+ # config/initializers/cruft_tracker.rb
338
+
339
+ CruftTracker.init do
340
+ are_any_of_these_methods_being_used? SomeClass
333
341
  end
334
342
  ```
335
343
 
336
344
  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
345
 
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.
346
+ 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 tracking only what you are specifically interested in.
347
+
348
+ ### Tracking Views
349
+
350
+ In additon to tracking methods, CruftTracker lets you track details about view rendering. Consider this scenario: You have a large legacy application with a ton views and partials. Over time your controllers have been changed and sometimes deleted. Perhaps a partial was once widely used, but isn't anymore. You might want to be able to easily tell if a given partial or view is being used. Once again, CruftTracker to the rescue!
351
+
352
+ Let's say you have this view and you're just not sure it's being used anymore:
353
+
354
+ ```erb
355
+ <!-- app/views/something/some_view.html.erb -->
356
+
357
+ <div>
358
+ <%- if @data.present? %>
359
+ <strong>Woo hoo!</strong>
360
+ <% end %>
361
+ </div>
362
+ ```
363
+
364
+ You can track renders of this view by using `is_this_view_used?` in the CruftTracker initializer:
365
+
366
+ ```ruby
367
+ # config/initializers/cruft_tracker.rb
368
+
369
+ CruftTracker.init do
370
+ is_this_view_used? 'app/views/something/some_view.html.erb'
371
+ end
372
+ ```
373
+
374
+ Note that the path provided is from the application's root.
375
+
376
+ As soon as Rails runs the CruftTracker initializer, CruftTracker will create a new record
377
+ in the `cruft_tracker_views` table that looks like this:
378
+
379
+
380
+ | id | view | renders | comment | deleted_at | created_at | updated_at |
381
+ | ---- | -------------------------------------- | ------- | ------- | ---------- | ------------------- | ------------------- |
382
+ | 1 | app/views/something/some_view.html.erb | 0 | null | null | 2022-01-21 14:07:48 | 2022-01-21 14:07:48 |
383
+
384
+ This record is accessible using the `CruftTracker::View` model. EG: `CruftTracker::View.find(1)`
385
+
386
+ The fields are:
387
+
388
+ - `id` - Shockingly, this is the primary key.
389
+ - `view` - This is the path to the view from the Rails application's root.
390
+ - `renders` - The number of times the view has been rendered.
391
+ - `comments` - This is a JSON field containing extra data provided to the option `comments:` argument for the `is_this_view_used?` method.
392
+ - `deleted_at` - When set, this indicates that the view is no longer being tracked.
393
+ - `created_at` - The date/time we started tracking the view.
394
+ - `updated_at` - The last time this record was updated. IE: the last time the tracked view was rendered.
395
+
396
+ Looking at this, we can see that the `app/views/something/some_view.html.erb` view has never been rendered. This is nice because it means that
397
+ you can track renders of views, but you can and also know when a view has _not_ been rendered. A similar record is created for every view you track
398
+ with `is_this_view_used?` You should always have records for potentially crufty
399
+ views, even if the view itself is never rendered.
400
+
401
+ So, having configured the view to be tracked, you can check this table after a while. If you see that there have been zero renders,
402
+ you have a reasonably good hint that the view may not actually be used. Of course, some things aren't used frequently. **Think before you delete!**
403
+
404
+ `is_this_view_used?` can be used to track ordinary views as well as partials. While only tested with ERB templates, it should work with any other template engine, such as HAML. If you run into problems, please open a Github issue!
405
+
406
+ ### Tracking View Rendering Details
407
+
408
+ Out of the box, you don't get a lot of information when tracking views. Basically, all you know is the number of times a view has been rendered. That's helpful, but not exactly amazing. Happily, CruftTracker provides a view helper that can be used to track a lot more information about view renders. The only downside is that it requires you to add a line of code to your templates.
409
+
410
+ Let's say you have this partial and you want to know not just if it's used, but _what uses it_.
411
+
412
+ ```erb
413
+ <!-- app/views/shared/whatever.html.erb -->
414
+
415
+ <div>I am a partial. Hear me roar.</div>
416
+
417
+ <div><%= some_value %></div>
418
+ ```
419
+
420
+ You'll still want to configure view tracking in the initializer:
421
+
422
+ ```ruby
423
+ # config/initializers/cruft_tracker.rb
424
+
425
+ CruftTracker.init do
426
+ is_this_view_used? 'app/views/shared/whatever.html.erb'
427
+ end
428
+ ```
429
+
430
+ However, to get additional details about renders of this partial, you can invoke the `record_cruft_tracker_view_render` helper into the view. It's recommended to add this on the first line of the view. EG:
431
+
432
+ ```erb
433
+ <!-- app/views/shared/whatever.html.erb -->
434
+
435
+ <%- record_cruft_tracker_view_render %>
436
+
437
+ <div>I am a partial. Hear me roar.</div>
438
+
439
+ <div><%= some_value %></div>
440
+ ```
441
+
442
+ When the view is actually rendered, CruftTracker will collect information about the render and store it in the `cruft_tracker_view_renders` table, like this:
443
+
444
+ | id | view_id | render_hash | controller | endpoint | route | render_stack | occurrences | created_at | updated_at |
445
+ | ---- | ------- | -------------------------------- | -------------- | ------------ | ---------------------- | ------------ | ----------- | ------------------- | ------------------- |
446
+ | 1 | 1 | 98cc3bd79cf6f3606c5afe3b9faf925b | SomeController | do_something | /foo/:id/bar(.:format) | [{...}] | 1 | 2022-07-06 15:29:06 | 2022-07-06 15:29:06 |
447
+
448
+ This record is represented by the `CruftTracker::ViewRender` model and can be loaded in the usual ways. You can also access it from a specific view record. EG: `CruftTracker::View.find(1).view_renders`, which will return an association of unique renders.
449
+
450
+ There's a lot packed into that record. Let's disect it:
451
+
452
+ * `id` - This is the primary key for the view render.
453
+
454
+ * `view_id` - This is the id of the view that was rendered.
455
+
456
+ * `render_hash` - This is the MD5 hash of the combination of the `controller`, `endpoint`, `route`, `http_method`, and the JSON version of the `render_stack`. The combination of these fields must be unique so that we can count the number of occurrences. The `render_hash` is a shortcut used by CruftTracker to easily determine if the combination of these items is unique. Basically, it's for optimization purposes and you shouldn't worry about it. :wink:
457
+
458
+ * `controller` - This is the class name for the controller that ultimately triggered rendering this view. Note that this controller is what kicked off the render, not necessarily what directly caused the view to be rendered. In the case of a partial, you could have a deeply nested set of renders. All the `controller` tells you is that a request to the controller ultimately caused the view to be rendered.
459
+
460
+ * `endpoint` - This is the method within the controller that caused the view to be rendered. The same caveats apply as with `controller`.
461
+
462
+ * `route` - This is the generic route that caused the view to be rendered. Note that this doesn't include the IDs, but the route's pattern for the IDs.
463
+
464
+ * `http_method` - This is the HTTP method used to access the `endpoint`.
465
+
466
+ * `render_stack` - This is an array that is _similar_ to a backtrace, but isn't actually a backtrace. To explain, if you call `render` in a controller, Rails doesn't actually render the view at that time. Instead, it ensues the view for rendering once the controller's method has finished executing. That means that if, while the view is rendering, you call invoke Ruby's `caller_locations`, you wouldn't see the controller that triggered the render. It was decided that this was generally pretty useless. Instead, what this field stores is the hierarchy of views / partials being rendered, but none of the other related code. It can provide a hint of how a deeply nested partial is rendered. Here's an example that shows a partial being rendered from a regular view:
467
+
468
+ ```ruby
469
+ [
470
+ { "path": "/app/spec/dummy/app/views/shared/_whatever.html.erb",
471
+ "label": "_app_views_shared__whatever_html_erb___436643032048620150_11500",
472
+ "lineno": 1,
473
+ "base_label": "_app_views_shared__whatever_html_erb___436643032048620150_11500" },
474
+ { "path": "/app/spec/dummy/app/views/main/show.html.erb",
475
+ "label": "_app_views_main_show_html_erb__2350809826889573468_11260",
476
+ "lineno": 6,
477
+ "base_label": "_app_views_main_show_html_erb__2350809826889573468_11260"}
478
+ ]
479
+ ```
480
+
481
+ * `occurrences` - This is the number of times that this particular set of details have been seen when rendering a particular view.
482
+
483
+ * `created_at` - The date/time we first saw this controller, endpoint, route, etc render the view.
484
+
485
+ * `updated_at` - The last time we first saw this controller, endpoint, route, etc render the view.
486
+
487
+ ### Tracking View Render Metadata
488
+
489
+ Knowing what caused a view to render is all well and good, but sometimes you might want a little more insight. Similar to how you can track arguments on methods with CruftTracker, you can track metadata for views. This is simply an argument you provide to the `record_cruft_tracker_view_render` method. For example:
490
+
491
+ ```erb
492
+ <!-- app/views/shared/whatever.html.erb -->
493
+
494
+ <%- record_cruft_tracker_view_render(some_value) %>
495
+
496
+ <div>I am a partial. Hear me roar.</div>
497
+
498
+ <div><%= some_value %></div>
499
+ ```
500
+
501
+ The metadata argument can be anything that is serializable to JSON. When you provide the argument, a record will be created or updated in the `cruft_tracker_render_metadata` table that looks like this:
502
+
503
+ | id | view_render_id | metadata_hash | metadata | occurrences | created_at | updated_at |
504
+ | ---- | -------------- | -------------------------------- | ------------------- | ----------- | ------------------- | ------------------- |
505
+ | 1 | 1 | a434e71475ff330064970fdc9fb123fc | ...your metadata... | 1 | 2022-07-06 16:07:38 | 2022-07-06 16:07:38 |
506
+
507
+ Let's break this down:
508
+
509
+ * `id` - Ye olde primary key
510
+ * `view_render_id` - This is the ID of the `CruftTracker::ViewRender` that this metadata is associated with.
511
+ * `metadata_hash` - This is the hash of the `metadata` field so that we can easily create a unique index.
512
+ * `occurrences` - The number of times that this exact metadata has been seen before.
513
+ * `created_at` - The first time we saw this metadata.
514
+ * `updated_at` - The most recent time we saw this metadata.
515
+
516
+ `CruftTracker::RenderMetadata` can be accessed from a `CruftTracker::ViewRender` via its `render_metadata` association.
517
+
518
+ A word of caution: don't track ever variable. Variables are, by definition, variable. Each time a view renders you could have a different value for a variable. Instead, this would be better used to track what variables are available. EG:
519
+
520
+ ```erb
521
+ <!-- app/views/shared/whatever.html.erb -->
522
+
523
+ <%- record_cruft_tracker_view_render(instance_variables) %>
524
+
525
+ <div>I am a partial. Hear me roar.</div>
526
+
527
+ <div><%= some_value %></div>
528
+ ```
339
529
 
340
530
  ### Clean Up
341
531
 
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.
532
+ CruftTacker automatically cleans up after itself. ✨🧹 If you remove any configured tracking, CruftTracker will recognize this when your application starts up and mark the associated `cruft_tracker_methods` record as deleted.
343
533
 
344
534
  ## API Docs
345
535
 
@@ -369,10 +559,10 @@ Returns an array of `CruftTracker::Method` instances.
369
559
 
370
560
  ##### Arguments
371
561
 
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. |
562
+ | Name | Type | Required? | Default | Description |
563
+ | ------------------ | :-------------------------------------- | --------- | ------- | ------------------------------------------------------------ |
564
+ | 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`. |
565
+ | 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
566
 
377
567
  ## Developing
378
568
 
@@ -1,4 +1,55 @@
1
1
  module CruftTracker
2
2
  module ApplicationHelper
3
+ def record_cruft_tracker_view_render(metadata = nil)
4
+ CruftTracker::RecordViewRender.run!(
5
+ view: cruft_tracker_view,
6
+ controller: controller.class.to_s,
7
+ endpoint: action_name,
8
+ route: route_path,
9
+ http_method: request.method,
10
+ render_stack: render_stack,
11
+ metadata: metadata
12
+ )
13
+ rescue StandardError
14
+ # Swallow errors
15
+ end
16
+
17
+ private
18
+
19
+ def cruft_tracker_view
20
+ path = render_stack.first[:path].gsub(/#{Rails.root}\//, "")
21
+ view = CruftTracker::View.find_by(view: path)
22
+
23
+ return view if view.present?
24
+
25
+ CruftTracker::TrackView.run!(view: path)
26
+ end
27
+
28
+ def route_path
29
+ _routes.router.recognize(request) do |route, _|
30
+ return route.path.spec.to_s
31
+ end
32
+
33
+ nil
34
+ end
35
+
36
+ def paths_to_views
37
+ @paths_to_views ||= view_paths.map { |view_path| view_path.to_path }
38
+ end
39
+
40
+ def render_stack
41
+ caller_locations.select do |caller_location|
42
+ paths_to_views.any? do |path_for_view|
43
+ caller_location.path.match?(/^#{path_for_view}\//)
44
+ end
45
+ end.map do |location|
46
+ {
47
+ path: location.path,
48
+ label: location.label,
49
+ base_label: location.base_label,
50
+ lineno: location.lineno
51
+ }
52
+ end
53
+ end
3
54
  end
4
55
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class RenderMetadata < ActiveRecord::Base
5
+ belongs_to :view_render, class_name: 'CruftTracker::ViewRender'
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class View < ActiveRecord::Base
5
+ has_many :backtraces, class_name: 'CruftTracker::Backtrace', as: :traceable
6
+ has_many :view_renders, class_name: 'CruftTracker::ViewRender'
7
+
8
+ def still_exists?
9
+ File.exist?(absolute_path)
10
+ end
11
+
12
+ def still_tracked?
13
+ return true if CruftTracker::Registry.include?(self)
14
+ return true if File.read(absolute_path).match?(/record_cruft_tracker_view_render/)
15
+
16
+ false
17
+ end
18
+
19
+ private
20
+
21
+ def absolute_path
22
+ "#{Rails.root}/#{view}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class ViewRender < ActiveRecord::Base
5
+ belongs_to :view, class_name: 'CruftTracker::View'
6
+ has_many :render_metadata, class_name: 'CruftTracker::RenderMetadata'
7
+ end
8
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class CleanupUntrackedViews < CruftTracker::ApplicationService
5
+ private
6
+
7
+ def execute
8
+ CruftTracker::LogSuppressor.suppress_logging do
9
+ CruftTracker::View
10
+ .where(deleted_at: nil)
11
+ .each do |view|
12
+ unless view.still_exists? && view.still_tracked?
13
+ view.update(deleted_at: Time.current)
14
+ end
15
+ end
16
+ end
17
+ rescue StandardError
18
+ # I'm actively ignoring all errors. Chances are, these are due to something like running rake
19
+ # tasks in CI when the DB doesn't already exist.
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class IncrementViewRenders < CruftTracker::ApplicationService
5
+ record :view, class: CruftTracker::View
6
+
7
+ def execute
8
+ CruftTracker::LogSuppressor.suppress_logging { increment_renders }
9
+ end
10
+
11
+ def increment_renders
12
+ view.update(renders: view.reload.renders + 1)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ # Records provided metadata associated with a given render
5
+ class RecordRenderMetadata < CruftTracker::ApplicationService
6
+ record :view_render, class: CruftTracker::ViewRender
7
+ interface :metadata, methods: %i[to_json], default: nil
8
+
9
+ private
10
+
11
+ def execute
12
+ return unless metadata.present?
13
+
14
+ CruftTracker::LogSuppressor.suppress_logging do
15
+ render_metadata_record.with_lock do
16
+ render_metadata_record.reload
17
+ render_metadata_record.update(occurrences: render_metadata_record.occurrences + 1)
18
+ end
19
+ end
20
+ end
21
+
22
+ def render_metadata_record
23
+ @render_metadata_record ||=
24
+ begin
25
+ CruftTracker::RenderMetadata.create(
26
+ view_render: view_render,
27
+ metadata_hash: metadata_hash,
28
+ metadata: metadata
29
+ )
30
+ rescue ActiveRecord::RecordNotUnique
31
+ CruftTracker::RenderMetadata.find_by(metadata_hash: metadata_hash)
32
+ end
33
+ end
34
+
35
+ def metadata_hash
36
+ Digest::MD5.hexdigest([view_render.render_hash, metadata].to_json)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ class RecordViewRender < CruftTracker::ApplicationService
5
+ record :view, class: CruftTracker::View
6
+ string :controller
7
+ string :endpoint
8
+ string :route
9
+ string :http_method
10
+ array :render_stack do
11
+ hash do
12
+ string :path
13
+ string :label
14
+ string :base_label
15
+ integer :lineno
16
+ end
17
+ end
18
+ interface :metadata, methods: %i[to_json], default: nil
19
+
20
+ private
21
+
22
+ def execute
23
+ CruftTracker::LogSuppressor.suppress_logging do
24
+ view.with_lock do
25
+ view.reload
26
+ view.update(renders: view.renders + 1)
27
+ end
28
+
29
+ view_render_record.with_lock do
30
+ view_render_record.reload
31
+ view_render_record.update(occurrences: view_render_record.occurrences + 1)
32
+
33
+ record_render_metadata(view_render_record)
34
+
35
+ view_render_record
36
+ end
37
+ end
38
+ end
39
+
40
+ def record_render_metadata(view_render_record)
41
+ compose(CruftTracker::RecordRenderMetadata,
42
+ view_render: view_render_record,
43
+ metadata: metadata)
44
+ end
45
+
46
+ def view_render_record
47
+ @view_render_record ||=
48
+ begin
49
+ CruftTracker::ViewRender.create(
50
+ view: view,
51
+ render_hash: render_hash,
52
+ controller: controller,
53
+ endpoint: endpoint,
54
+ route: route,
55
+ http_method: http_method,
56
+ render_stack: render_stack
57
+ )
58
+ rescue ActiveRecord::RecordNotUnique
59
+ CruftTracker::ViewRender.find_by(render_hash: render_hash)
60
+ end
61
+ end
62
+
63
+ def render_hash
64
+ Digest::MD5.hexdigest(
65
+ {
66
+ controller: controller,
67
+ endpoint: endpoint,
68
+ route: route,
69
+ http_method: http_method,
70
+ render_stack: render_stack.to_json
71
+ }.to_json
72
+ )
73
+ end
74
+
75
+ end
76
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # require 'cruft_tracker/record_invocation'
4
- # require 'cruft_tracker/record_backtrace'
5
3
  module CruftTracker
6
4
  class TrackMethod < CruftTracker::ApplicationService
7
5
  private
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CruftTracker
4
+ # creates or updates a CruftTracker:View record and configures subscriptions for render events
5
+ class TrackView < CruftTracker::ApplicationService
6
+ private
7
+
8
+ string :view
9
+ interface :comment, methods: %i[to_json], default: nil
10
+
11
+ def execute
12
+ view_record = CruftTracker::LogSuppressor.suppress_logging do
13
+ view_record = create_or_find_view_record
14
+ view_record.deleted_at = nil
15
+ view_record.comment = comment if comment != view_record.comment
16
+ view_record.save
17
+ view_record
18
+ end
19
+
20
+ listen_for_render(view_record)
21
+
22
+ CruftTracker::Registry << view_record
23
+
24
+ view_record
25
+ rescue ActiveRecord::StatementInvalid => e
26
+ raise unless e.cause.present? && e.cause.instance_of?(Mysql2::Error)
27
+
28
+ Rails.logger.warn(
29
+ 'CruftTracker was unable to record a view. Does the cruft_tracker_views table exist? Have migrations been run?'
30
+ )
31
+ rescue NoMethodError
32
+ Rails.logger.warn(
33
+ 'CruftTracker was unable to record a view. Have migrations been run?'
34
+ )
35
+ rescue Mysql2::Error::ConnectionError,
36
+ ActiveRecord::ConnectionNotEstablished
37
+ Rails.logger.warn(
38
+ 'CruftTracker was unable to record a view due to being unable to connect to the database. This may be a non-issue in cases where the database is intentionally not available.'
39
+ )
40
+ end
41
+
42
+ def listen_for_render(view_record)
43
+ ActiveSupport::Notifications.subscribe /!render_.*\.action_view/ do |*args|
44
+ event = ActiveSupport::Notifications::Event.new(*args)
45
+ if event.payload[:identifier] == "#{Rails.root}/#{view}"
46
+ CruftTracker::IncrementViewRenders.run!(view: view_record.id)
47
+ end
48
+ rescue StandardError
49
+ # suppress errors
50
+ end
51
+ end
52
+
53
+ def create_or_find_view_record
54
+ CruftTracker::View.create(
55
+ view: view,
56
+ comment: comment
57
+ )
58
+ rescue ActiveRecord::RecordNotUnique
59
+ CruftTracker::View.find_by(
60
+ view: view
61
+ )
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,13 @@
1
+ class CreateCruftTrackerViews < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :cruft_tracker_views do |t|
4
+ t.string :view, null: false
5
+ t.integer :renders, null: false, default: 0
6
+ t.json :comment, null: true
7
+ t.datetime :deleted_at
8
+ t.timestamps
9
+
10
+ t.index :view, unique: true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ class CreateCruftTrackerViewRenders < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :cruft_tracker_view_renders do |t|
4
+ t.references :view, null: false
5
+ t.string :render_hash, null: false, index: true, unique: true
6
+ t.string :controller, null: false, index: true
7
+ t.string :endpoint, null: false, index: true
8
+ t.string :route, null: false, index: true
9
+ t.string :http_method, null: false, index: true
10
+ t.json :render_stack, null: false
11
+ t.integer :occurrences, null: false, index: true, default: 0
12
+
13
+ t.timestamps
14
+
15
+ t.index %i[view_id render_hash], unique: true
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ class CreateRenderMetadata < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :cruft_tracker_render_metadata do |t|
4
+ t.references :view_render, null: false
5
+ t.string :metadata_hash, null: false
6
+ t.json :metadata, null: false
7
+ t.integer :occurrences, null: false, index: true, default: 0
8
+ t.timestamps
9
+
10
+ t.index :metadata_hash, unique: true
11
+ end
12
+ end
13
+ end
@@ -4,14 +4,16 @@ module CruftTracker
4
4
 
5
5
  config.after_initialize do
6
6
  CruftTracker::CleanupUntrackedMethods.run!
7
+ CruftTracker::CleanupUntrackedViews.run!
7
8
  rescue StandardError
8
9
  # Swallow all errors to prevent initialization failures.
9
10
  end
10
11
 
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
12
+ initializer 'local_helper.action_controller' do
13
+ ActiveSupport.on_load :action_controller do
14
+ helper CruftTracker::ApplicationHelper
15
+ end
16
+ end
17
+
16
18
  end
17
19
  end
@@ -1,3 +1,3 @@
1
1
  module CruftTracker
2
- VERSION = '0.1.4'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/cruft_tracker.rb CHANGED
@@ -4,10 +4,18 @@ require 'cruft_tracker/registry'
4
4
  require 'cruft_tracker/log_suppressor'
5
5
 
6
6
  module CruftTracker
7
- # Your code goes here...
7
+ def self.init(&block)
8
+ self.instance_eval(&block)
9
+ end
8
10
 
9
- def self.is_this_view_used?
10
- puts '>>>> is this view used?'
11
+ def self.is_this_view_used?(
12
+ view,
13
+ comment: nil
14
+ )
15
+ CruftTracker::TrackView.run!(
16
+ view: view,
17
+ comment: comment
18
+ )
11
19
  end
12
20
 
13
21
  def self.is_this_method_used?(
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cruft_tracker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adwerx Inc.
8
8
  - Doug Hughes
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-04-25 00:00:00.000000000 Z
12
+ date: 2022-07-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: active_interaction
@@ -262,13 +262,21 @@ files:
262
262
  - app/models/cruft_tracker/argument.rb
263
263
  - app/models/cruft_tracker/backtrace.rb
264
264
  - app/models/cruft_tracker/method.rb
265
+ - app/models/cruft_tracker/render_metadata.rb
266
+ - app/models/cruft_tracker/view.rb
267
+ - app/models/cruft_tracker/view_render.rb
265
268
  - app/services/cruft_tracker/application_service.rb
266
269
  - app/services/cruft_tracker/cleanup_untracked_methods.rb
270
+ - app/services/cruft_tracker/cleanup_untracked_views.rb
271
+ - app/services/cruft_tracker/increment_view_renders.rb
267
272
  - app/services/cruft_tracker/record_arguments.rb
268
273
  - app/services/cruft_tracker/record_backtrace.rb
269
274
  - app/services/cruft_tracker/record_invocation.rb
275
+ - app/services/cruft_tracker/record_render_metadata.rb
276
+ - app/services/cruft_tracker/record_view_render.rb
270
277
  - app/services/cruft_tracker/track_all_methods.rb
271
278
  - app/services/cruft_tracker/track_method.rb
279
+ - app/services/cruft_tracker/track_view.rb
272
280
  - app/views/cruft_tracker/methods/index.html.erb
273
281
  - app/views/layouts/cruft_tracker/application.html.erb
274
282
  - config/routes.rb
@@ -276,6 +284,9 @@ files:
276
284
  - db/migrate/20220418133030_create_cruft_tracker_backtraces.rb
277
285
  - db/migrate/20220419171326_add_comment_to_cruft_tracker_methods.rb
278
286
  - db/migrate/20220419174055_create_cruft_tracker_arguments.rb
287
+ - db/migrate/20220628175222_create_cruft_tracker_views.rb
288
+ - db/migrate/20220701125925_create_cruft_tracker_view_renders.rb
289
+ - db/migrate/20220705152409_create_render_metadata.rb
279
290
  - lib/cruft_tracker.rb
280
291
  - lib/cruft_tracker/engine.rb
281
292
  - lib/cruft_tracker/log_suppressor.rb
@@ -288,7 +299,7 @@ licenses:
288
299
  metadata:
289
300
  homepage_uri: https://github.com/AdWerx/cruft-tracker
290
301
  source_code_uri: https://github.com/AdWerx/cruft-tracker
291
- post_install_message:
302
+ post_install_message:
292
303
  rdoc_options: []
293
304
  require_paths:
294
305
  - lib
@@ -303,8 +314,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
303
314
  - !ruby/object:Gem::Version
304
315
  version: '0'
305
316
  requirements: []
306
- rubygems_version: 3.0.3.1
307
- signing_key:
317
+ rubygems_version: 3.1.6
318
+ signing_key:
308
319
  specification_version: 4
309
320
  summary: Provides a system to track Ruby method usage somewhat unobtrusively.
310
321
  test_files: []