routes-analyzer 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +43 -41
- data/lib/routes/analyzer/middleware.rb +8 -31
- data/lib/routes/analyzer/route_usage_tracker.rb +31 -25
- data/lib/routes/analyzer/version.rb +1 -1
- data/lib/tasks/routes/analyzer_tasks.rake +24 -25
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3cae3e4dca655b13929955c6357b4351607dd32ae29331c643666e6d05562e32
|
|
4
|
+
data.tar.gz: 3b533755ae2bc4b810708ac9d705085f5467c3cb94714a458baa6aaa8f5eaebf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d47e92d852c70f7ce7108777532aea5f5650f562fe31607d522b178bc206ed8430f713408cb8e28989d38ff8c66c3c5f64bf4ac87407e928bff9f450b7189255
|
|
7
|
+
data.tar.gz: d3c981912a2b4894895f4f19d80842d878f6aa1edb1ec157a34e05d54e8ef67cd7c490ed055ef7d1bfd8187277e0f4d7eb923d52d127669eeaedcdcf3dea8a03
|
data/README.md
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/routes-analyzer)
|
|
4
4
|
|
|
5
|
-
A Ruby on Rails plugin that tracks and analyzes
|
|
5
|
+
A Ruby on Rails plugin that tracks and analyzes controller action usage in your application. It uses Redis to store action access patterns and provides insights into which controller actions are being used and which are not.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
9
|
+
- **Controller Action Tracking**: Automatically tracks which controller actions are accessed and how often
|
|
10
|
+
- **Method-Agnostic Tracking**: Actions are tracked by controller#action regardless of HTTP method (GET, POST, etc.)
|
|
11
11
|
- **Redis Storage**: Uses Redis to store usage statistics efficiently
|
|
12
12
|
- **Configurable Timeframe**: Set custom analysis periods (default 30 days)
|
|
13
|
-
- **Comprehensive Reporting**: Shows both used and unused
|
|
13
|
+
- **Comprehensive Reporting**: Shows both used and unused controller actions
|
|
14
14
|
- **Rake Tasks**: Easy-to-use commands for viewing statistics and managing data
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
@@ -41,10 +41,10 @@ Routes::Analyzer.configure do |config|
|
|
|
41
41
|
# Redis connection URL
|
|
42
42
|
config.redis_url = ENV['REDIS_URL'] || 'redis://localhost:6379/0'
|
|
43
43
|
|
|
44
|
-
# Redis key prefix for storing
|
|
44
|
+
# Redis key prefix for storing action usage data
|
|
45
45
|
config.redis_key_prefix = 'routes_analyzer'
|
|
46
46
|
|
|
47
|
-
# Timeframe for
|
|
47
|
+
# Timeframe for action usage analysis in days
|
|
48
48
|
config.timeframe = 30
|
|
49
49
|
end
|
|
50
50
|
```
|
|
@@ -59,38 +59,39 @@ Alternatively, you can create the configuration file manually in `config/initial
|
|
|
59
59
|
|
|
60
60
|
## Usage
|
|
61
61
|
|
|
62
|
-
Once installed and configured, the middleware will automatically start tracking
|
|
62
|
+
Once installed and configured, the middleware will automatically start tracking controller action usage. No additional code changes are required.
|
|
63
63
|
|
|
64
64
|
### Rake Tasks
|
|
65
65
|
|
|
66
|
-
#### View
|
|
66
|
+
#### View Controller Action Usage Statistics
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
69
|
bundle exec rake routes:analyzer:usage
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
This command shows:
|
|
73
|
-
- All
|
|
74
|
-
- Usage count for each
|
|
73
|
+
- All controller actions available in your application
|
|
74
|
+
- Usage count for each action in the specified timeframe
|
|
75
75
|
- Last access timestamp
|
|
76
|
-
- Summary statistics (total, used, unused
|
|
76
|
+
- Summary statistics (total, used, unused actions)
|
|
77
77
|
|
|
78
78
|
Example output:
|
|
79
79
|
```
|
|
80
|
-
|
|
80
|
+
Controller Action Usage Analysis (30 days)
|
|
81
81
|
================================================================================
|
|
82
82
|
|
|
83
|
-
COUNT
|
|
83
|
+
COUNT CONTROLLER#ACTION METHOD LAST ACCESSED
|
|
84
84
|
--------------------------------------------------------------------------------
|
|
85
|
-
45
|
|
86
|
-
23
|
|
87
|
-
12
|
|
88
|
-
|
|
89
|
-
0 /
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
45 users#index GET 2025-06-18 14:30
|
|
86
|
+
23 users#show GET 2025-06-18 12:15
|
|
87
|
+
12 posts#index GET 2025-06-17 09:45
|
|
88
|
+
5 posts#create POST 2025-06-16 16:20
|
|
89
|
+
0 admin/reports#index GET Never
|
|
90
|
+
0 api/v1/health#check GET Never
|
|
91
|
+
|
|
92
|
+
Total actions: 6
|
|
93
|
+
Used actions: 4
|
|
94
|
+
Unused actions: 2
|
|
94
95
|
```
|
|
95
96
|
|
|
96
97
|
#### Clear Usage Data
|
|
@@ -99,7 +100,7 @@ Unused routes: 2
|
|
|
99
100
|
bundle exec rake routes:analyzer:clear
|
|
100
101
|
```
|
|
101
102
|
|
|
102
|
-
Removes all stored
|
|
103
|
+
Removes all stored controller action usage data from Redis.
|
|
103
104
|
|
|
104
105
|
#### Check Configuration
|
|
105
106
|
|
|
@@ -112,13 +113,13 @@ Displays current configuration and tests Redis connectivity.
|
|
|
112
113
|
## How It Works
|
|
113
114
|
|
|
114
115
|
1. **Middleware Integration**: The plugin automatically adds middleware to your Rails application
|
|
115
|
-
2. **
|
|
116
|
+
2. **Controller Action Filtering**: Only controller actions from defined routes in `routes.rb` are tracked. This ensures that:
|
|
116
117
|
- Undefined routes (404 errors) are not tracked
|
|
117
118
|
- Catch-all routes that handle unknown paths don't pollute the data
|
|
118
|
-
- Only legitimate application
|
|
119
|
-
3. **Request Tracking**: Each valid HTTP request is analyzed to extract
|
|
119
|
+
- Only legitimate application controller actions are analyzed
|
|
120
|
+
3. **Request Tracking**: Each valid HTTP request is analyzed to extract controller and action information
|
|
120
121
|
4. **Redis Storage**: Usage data is stored in Redis with the following structure:
|
|
121
|
-
-
|
|
122
|
+
- Controller name and action name
|
|
122
123
|
- Access count within the timeframe
|
|
123
124
|
- Last access timestamp
|
|
124
125
|
5. **Data Expiration**: Redis keys automatically expire after the configured timeframe plus a buffer period
|
|
@@ -133,35 +134,36 @@ The middleware uses Rails' routing system to determine if a route is valid:
|
|
|
133
134
|
|
|
134
135
|
This approach ensures that only routes you've intentionally defined are included in the usage analysis.
|
|
135
136
|
|
|
136
|
-
##
|
|
137
|
+
## Controller Action Tracking
|
|
137
138
|
|
|
138
|
-
The gem
|
|
139
|
+
The gem tracks usage by controller and action rather than by specific URL paths. This provides more meaningful insights into which parts of your application are being used:
|
|
139
140
|
|
|
140
|
-
- **Route Definition**:
|
|
141
|
-
- **Actual Requests**:
|
|
142
|
-
- **Tracked As**:
|
|
141
|
+
- **Route Definition**: `resources :users` in `routes.rb`
|
|
142
|
+
- **Actual Requests**: `GET /users/123`, `GET /users/456`, `POST /users/789/update`
|
|
143
|
+
- **Tracked As**: Separate entries for `users#show` and `users#update` actions
|
|
143
144
|
|
|
144
|
-
This means that accessing `/users/123
|
|
145
|
+
This means that accessing different user IDs (e.g., `/users/123`, `/users/456`, `/users/789`) will all be counted under the same `users#show` action, giving you a clear picture of which controller actions are being utilized.
|
|
145
146
|
|
|
146
147
|
**Example Output:**
|
|
147
148
|
```
|
|
148
|
-
COUNT
|
|
149
|
+
COUNT CONTROLLER#ACTION METHOD LAST ACCESSED
|
|
149
150
|
--------------------------------------------------------------
|
|
150
|
-
15
|
|
151
|
-
8
|
|
152
|
-
23
|
|
151
|
+
15 users#show GET 2025-06-18 14:30
|
|
152
|
+
8 users#update PATCH 2025-06-18 12:15
|
|
153
|
+
23 posts#show GET 2025-06-17 09:45
|
|
153
154
|
```
|
|
154
155
|
|
|
155
|
-
This grouping provides much more meaningful insights into which
|
|
156
|
+
This grouping provides much more meaningful insights into which parts of your application are being used.
|
|
156
157
|
|
|
157
158
|
## Data Structure
|
|
158
159
|
|
|
159
|
-
For each tracked
|
|
160
|
+
For each tracked controller action, the following data is stored in Redis:
|
|
160
161
|
|
|
161
162
|
```ruby
|
|
162
163
|
{
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
controller: "users", # Controller name
|
|
165
|
+
action: "show", # Action name
|
|
166
|
+
method: "GET", # HTTP method (for reference)
|
|
165
167
|
count: 15, # Number of accesses
|
|
166
168
|
last_accessed: 1718721600 # Unix timestamp
|
|
167
169
|
}
|
|
@@ -38,7 +38,7 @@ module Routes
|
|
|
38
38
|
|
|
39
39
|
return unless route_info
|
|
40
40
|
|
|
41
|
-
redis_key = build_redis_key(route_info[:
|
|
41
|
+
redis_key = build_redis_key(route_info[:controller], route_info[:action])
|
|
42
42
|
current_time = Time.current
|
|
43
43
|
|
|
44
44
|
configuration.redis_client.multi do |redis|
|
|
@@ -48,8 +48,9 @@ module Routes
|
|
|
48
48
|
# Update last accessed timestamp
|
|
49
49
|
redis.hset(redis_key, "last_accessed", current_time.to_i)
|
|
50
50
|
|
|
51
|
-
# Set
|
|
52
|
-
redis.hset(redis_key, "
|
|
51
|
+
# Set controller#action and method info (in case it's the first time)
|
|
52
|
+
redis.hset(redis_key, "controller", route_info[:controller])
|
|
53
|
+
redis.hset(redis_key, "action", route_info[:action])
|
|
53
54
|
redis.hset(redis_key, "method", route_info[:method])
|
|
54
55
|
|
|
55
56
|
# Set expiration based on timeframe (add some buffer)
|
|
@@ -94,17 +95,10 @@ module Routes
|
|
|
94
95
|
controller_name = controller.controller_name
|
|
95
96
|
method = request.request_method.upcase
|
|
96
97
|
|
|
97
|
-
# Find the route pattern instead of using the actual path
|
|
98
|
-
route_path = find_route_pattern(controller_name, action, method)
|
|
99
|
-
|
|
100
|
-
# Fallback to actual path if pattern not found
|
|
101
|
-
route_path ||= request.path_info
|
|
102
|
-
|
|
103
98
|
{
|
|
104
|
-
route: route_path,
|
|
105
|
-
method: method,
|
|
106
99
|
controller: controller_name,
|
|
107
|
-
action: action
|
|
100
|
+
action: action,
|
|
101
|
+
method: method
|
|
108
102
|
}
|
|
109
103
|
end
|
|
110
104
|
rescue => e
|
|
@@ -112,25 +106,8 @@ module Routes
|
|
|
112
106
|
nil
|
|
113
107
|
end
|
|
114
108
|
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
Rails.application.routes.routes.each do |route|
|
|
119
|
-
if route.defaults[:controller] == controller_name &&
|
|
120
|
-
route.defaults[:action] == action &&
|
|
121
|
-
route.verb == method
|
|
122
|
-
return route.path.spec.to_s.gsub(/\(\.:format\)$/, "")
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
nil
|
|
127
|
-
rescue => e
|
|
128
|
-
Rails.logger.warn "Routes::Analyzer: Failed to find route pattern: #{e.message}"
|
|
129
|
-
nil
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def build_redis_key(route, method)
|
|
133
|
-
"#{configuration.redis_key_prefix}:#{method}:#{route.gsub('/', ':')}"
|
|
109
|
+
def build_redis_key(controller, action)
|
|
110
|
+
"#{configuration.redis_key_prefix}:#{controller}##{action}"
|
|
134
111
|
end
|
|
135
112
|
|
|
136
113
|
def configuration
|
|
@@ -9,7 +9,7 @@ module Routes
|
|
|
9
9
|
def get_usage_stats
|
|
10
10
|
cutoff_time = @configuration.timeframe.days.ago.to_i
|
|
11
11
|
|
|
12
|
-
# Get all
|
|
12
|
+
# Get all controller#action keys
|
|
13
13
|
pattern = "#{@configuration.redis_key_prefix}:*"
|
|
14
14
|
keys = @redis.keys(pattern)
|
|
15
15
|
|
|
@@ -24,7 +24,8 @@ module Routes
|
|
|
24
24
|
next if last_accessed && last_accessed < cutoff_time
|
|
25
25
|
|
|
26
26
|
usage_stats << {
|
|
27
|
-
|
|
27
|
+
controller: route_data["controller"],
|
|
28
|
+
action: route_data["action"],
|
|
28
29
|
method: route_data["method"],
|
|
29
30
|
count: route_data["count"]&.to_i || 0,
|
|
30
31
|
last_accessed: last_accessed ? Time.at(last_accessed) : nil
|
|
@@ -35,54 +36,59 @@ module Routes
|
|
|
35
36
|
usage_stats.sort_by { |stat| -stat[:count] }
|
|
36
37
|
end
|
|
37
38
|
|
|
38
|
-
def
|
|
39
|
+
def get_all_defined_actions
|
|
39
40
|
return [] unless defined?(Rails) && Rails.application
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
actions = []
|
|
43
|
+
Rails.application.routes.routes.each do |route|
|
|
44
|
+
next unless route.defaults[:controller] && route.defaults[:action]
|
|
45
|
+
|
|
46
|
+
actions << {
|
|
45
47
|
controller: route.defaults[:controller],
|
|
46
48
|
action: route.defaults[:action],
|
|
49
|
+
method: route.verb,
|
|
50
|
+
route: route.path.spec.to_s.gsub(/\(\.:format\)$/, ""),
|
|
47
51
|
name: route.name
|
|
48
52
|
}
|
|
49
|
-
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Remove duplicates based on controller#action (not method-specific)
|
|
56
|
+
actions.uniq { |a| "#{a[:controller]}##{a[:action]}" }
|
|
50
57
|
end
|
|
51
58
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
def merge_with_defined_actions(usage_stats)
|
|
60
|
+
defined_actions = get_all_defined_actions
|
|
61
|
+
usage_by_action = usage_stats.index_by { |stat| "#{stat[:controller]}##{stat[:action]}" }
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
merged_actions = []
|
|
57
64
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
usage_stat =
|
|
65
|
+
defined_actions.each do |defined_action|
|
|
66
|
+
action_key = "#{defined_action[:controller]}##{defined_action[:action]}"
|
|
67
|
+
usage_stat = usage_by_action[action_key]
|
|
61
68
|
|
|
62
69
|
if usage_stat
|
|
63
|
-
|
|
70
|
+
merged_actions << defined_action.merge(usage_stat)
|
|
64
71
|
else
|
|
65
|
-
|
|
72
|
+
merged_actions << defined_action.merge(
|
|
66
73
|
count: 0,
|
|
67
74
|
last_accessed: nil
|
|
68
75
|
)
|
|
69
76
|
end
|
|
70
77
|
end
|
|
71
78
|
|
|
72
|
-
# Add any tracked
|
|
79
|
+
# Add any tracked actions that aren't in the defined actions (in case of dynamic actions)
|
|
73
80
|
usage_stats.each do |usage_stat|
|
|
74
|
-
|
|
75
|
-
unless
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
action: nil,
|
|
81
|
+
action_key = "#{usage_stat[:controller]}##{usage_stat[:action]}"
|
|
82
|
+
unless defined_actions.any? { |da| "#{da[:controller]}##{da[:action]}" == action_key }
|
|
83
|
+
merged_actions << usage_stat.merge(
|
|
84
|
+
route: nil,
|
|
79
85
|
name: nil
|
|
80
86
|
)
|
|
81
87
|
end
|
|
82
88
|
end
|
|
83
89
|
|
|
84
|
-
# Sort by count (descending), then by
|
|
85
|
-
|
|
90
|
+
# Sort by count (descending), then by controller#action name
|
|
91
|
+
merged_actions.sort_by { |action| [ -action[:count], "#{action[:controller]}##{action[:action]}" ] }
|
|
86
92
|
end
|
|
87
93
|
end
|
|
88
94
|
end
|
|
@@ -1,60 +1,59 @@
|
|
|
1
1
|
namespace :routes do
|
|
2
2
|
namespace :analyzer do
|
|
3
|
-
desc "Show
|
|
3
|
+
desc "Show controller action usage statistics"
|
|
4
4
|
task usage: :environment do
|
|
5
5
|
begin
|
|
6
6
|
config = Routes::Analyzer.configuration
|
|
7
7
|
tracker = Routes::Analyzer::RouteUsageTracker.new(config)
|
|
8
8
|
|
|
9
|
-
puts "
|
|
9
|
+
puts "Controller Action Usage Analysis (#{config.timeframe} days)"
|
|
10
10
|
puts "=" * 80
|
|
11
11
|
puts
|
|
12
12
|
|
|
13
13
|
if config.valid?
|
|
14
14
|
usage_stats = tracker.get_usage_stats
|
|
15
|
-
|
|
15
|
+
merged_actions = tracker.merge_with_defined_actions(usage_stats)
|
|
16
16
|
else
|
|
17
|
-
puts "Warning: Redis not configured. Showing defined
|
|
17
|
+
puts "Warning: Redis not configured. Showing defined actions only."
|
|
18
18
|
puts
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
merged_actions = tracker.get_all_defined_actions.map do |action|
|
|
20
|
+
action.merge(count: 0, last_accessed: nil)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
if
|
|
25
|
-
puts "No
|
|
24
|
+
if merged_actions.empty?
|
|
25
|
+
puts "No controller actions found."
|
|
26
26
|
next
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
# Print header
|
|
30
|
-
printf "%-8s %-40s %-15s
|
|
30
|
+
printf "%-8s %-40s %-15s %s\n", "COUNT", "CONTROLLER#ACTION", "METHOD", "LAST ACCESSED"
|
|
31
31
|
puts "-" * 80
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
controller_action = if
|
|
35
|
-
"#{
|
|
33
|
+
merged_actions.each do |action|
|
|
34
|
+
controller_action = if action[:controller] && action[:action]
|
|
35
|
+
"#{action[:controller]}##{action[:action]}"
|
|
36
36
|
else
|
|
37
37
|
"N/A"
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
last_accessed = if
|
|
41
|
-
|
|
40
|
+
last_accessed = if action[:last_accessed]
|
|
41
|
+
action[:last_accessed].strftime("%Y-%m-%d %H:%M")
|
|
42
42
|
else
|
|
43
43
|
"Never"
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
printf "%-8d %-40s %-15s
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
controller_action[0, 20],
|
|
46
|
+
printf "%-8d %-40s %-15s %s\n",
|
|
47
|
+
action[:count],
|
|
48
|
+
controller_action[0, 40],
|
|
49
|
+
action[:method],
|
|
51
50
|
last_accessed
|
|
52
51
|
end
|
|
53
52
|
|
|
54
53
|
puts
|
|
55
|
-
puts "Total
|
|
56
|
-
puts "Used
|
|
57
|
-
puts "Unused
|
|
54
|
+
puts "Total actions: #{merged_actions.count}"
|
|
55
|
+
puts "Used actions: #{merged_actions.count { |a| a[:count] > 0 }}"
|
|
56
|
+
puts "Unused actions: #{merged_actions.count { |a| a[:count] == 0 }}"
|
|
58
57
|
|
|
59
58
|
rescue => e
|
|
60
59
|
puts "Error: #{e.message}"
|
|
@@ -69,7 +68,7 @@ namespace :routes do
|
|
|
69
68
|
end
|
|
70
69
|
end
|
|
71
70
|
|
|
72
|
-
desc "Clear all
|
|
71
|
+
desc "Clear all controller action usage data"
|
|
73
72
|
task clear: :environment do
|
|
74
73
|
begin
|
|
75
74
|
config = Routes::Analyzer.configuration
|
|
@@ -79,9 +78,9 @@ namespace :routes do
|
|
|
79
78
|
|
|
80
79
|
if keys.any?
|
|
81
80
|
redis.del(*keys)
|
|
82
|
-
puts "Cleared #{keys.count}
|
|
81
|
+
puts "Cleared #{keys.count} controller action usage records."
|
|
83
82
|
else
|
|
84
|
-
puts "No
|
|
83
|
+
puts "No controller action usage data found to clear."
|
|
85
84
|
end
|
|
86
85
|
|
|
87
86
|
rescue => e
|