routes-analyzer 0.1.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d59454435cfcca8978b29560a38ecd2457cdfd1ac6486be5a71fa9c9b24ea443
4
- data.tar.gz: 9c15d8ccb50eb9a2acc1a70c525deede37594200aca4b2bc1f08f9ab857a36ec
3
+ metadata.gz: 3cae3e4dca655b13929955c6357b4351607dd32ae29331c643666e6d05562e32
4
+ data.tar.gz: 3b533755ae2bc4b810708ac9d705085f5467c3cb94714a458baa6aaa8f5eaebf
5
5
  SHA512:
6
- metadata.gz: a85f24d238c05674ae36a97c30f36aa2d2913c77d53a2907c39a99203c2c1d77e93f86533c8cbbcbad7abbf0b56835e468906e04548075d609a0716ac42132bd
7
- data.tar.gz: bded888809fdda525cf230a50a9bad7fce9e705716d325257f931f2882524a10e6338d46ec820e1cb67e5c9f674ec89011cfde4ea19283edce0795fba943448e
6
+ metadata.gz: d47e92d852c70f7ce7108777532aea5f5650f562fe31607d522b178bc206ed8430f713408cb8e28989d38ff8c66c3c5f64bf4ac87407e928bff9f450b7189255
7
+ data.tar.gz: d3c981912a2b4894895f4f19d80842d878f6aa1edb1ec157a34e05d54e8ef67cd7c490ed055ef7d1bfd8187277e0f4d7eb923d52d127669eeaedcdcf3dea8a03
data/README.md CHANGED
@@ -2,14 +2,15 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/routes-analyzer.svg)](https://badge.fury.io/rb/routes-analyzer)
4
4
 
5
- A Ruby on Rails plugin that tracks and analyzes route usage in your application. It uses Redis to store route access patterns and provides insights into which routes are being used and which are not.
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
- - **Route Usage Tracking**: Automatically tracks which routes are accessed and how often
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.)
10
11
  - **Redis Storage**: Uses Redis to store usage statistics efficiently
11
12
  - **Configurable Timeframe**: Set custom analysis periods (default 30 days)
12
- - **Comprehensive Reporting**: Shows both used and unused routes
13
+ - **Comprehensive Reporting**: Shows both used and unused controller actions
13
14
  - **Rake Tasks**: Easy-to-use commands for viewing statistics and managing data
14
15
 
15
16
  ## Installation
@@ -40,10 +41,10 @@ Routes::Analyzer.configure do |config|
40
41
  # Redis connection URL
41
42
  config.redis_url = ENV['REDIS_URL'] || 'redis://localhost:6379/0'
42
43
 
43
- # Redis key prefix for storing route usage data
44
+ # Redis key prefix for storing action usage data
44
45
  config.redis_key_prefix = 'routes_analyzer'
45
46
 
46
- # Timeframe for route usage analysis in days
47
+ # Timeframe for action usage analysis in days
47
48
  config.timeframe = 30
48
49
  end
49
50
  ```
@@ -58,38 +59,39 @@ Alternatively, you can create the configuration file manually in `config/initial
58
59
 
59
60
  ## Usage
60
61
 
61
- Once installed and configured, the middleware will automatically start tracking route usage. No additional code changes are required.
62
+ Once installed and configured, the middleware will automatically start tracking controller action usage. No additional code changes are required.
62
63
 
63
64
  ### Rake Tasks
64
65
 
65
- #### View Route Usage Statistics
66
+ #### View Controller Action Usage Statistics
66
67
 
67
68
  ```bash
68
69
  bundle exec rake routes:analyzer:usage
69
70
  ```
70
71
 
71
72
  This command shows:
72
- - All routes defined in your `routes.rb` file
73
- - Usage count for each route in the specified timeframe
73
+ - All controller actions available in your application
74
+ - Usage count for each action in the specified timeframe
74
75
  - Last access timestamp
75
- - Summary statistics (total, used, unused routes)
76
+ - Summary statistics (total, used, unused actions)
76
77
 
77
78
  Example output:
78
79
  ```
79
- Routes Usage Analysis (30 days)
80
+ Controller Action Usage Analysis (30 days)
80
81
  ================================================================================
81
82
 
82
- COUNT ROUTE METHOD CONTROLLER#ACTION LAST ACCESSED
83
+ COUNT CONTROLLER#ACTION METHOD LAST ACCESSED
83
84
  --------------------------------------------------------------------------------
84
- 45 /users GET users#index 2025-06-18 14:30
85
- 23 /users/:id GET users#show 2025-06-18 12:15
86
- 12 /posts GET posts#index 2025-06-17 09:45
87
- 0 /admin/reports GET admin/reports#index Never
88
- 0 /api/v1/health GET api/v1/health#check Never
89
-
90
- Total routes: 5
91
- Used routes: 3
92
- Unused routes: 2
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
93
95
  ```
94
96
 
95
97
  #### Clear Usage Data
@@ -98,7 +100,7 @@ Unused routes: 2
98
100
  bundle exec rake routes:analyzer:clear
99
101
  ```
100
102
 
101
- Removes all stored route usage data from Redis.
103
+ Removes all stored controller action usage data from Redis.
102
104
 
103
105
  #### Check Configuration
104
106
 
@@ -111,13 +113,13 @@ Displays current configuration and tests Redis connectivity.
111
113
  ## How It Works
112
114
 
113
115
  1. **Middleware Integration**: The plugin automatically adds middleware to your Rails application
114
- 2. **Route Filtering**: Only routes explicitly defined in `routes.rb` are tracked. This ensures that:
116
+ 2. **Controller Action Filtering**: Only controller actions from defined routes in `routes.rb` are tracked. This ensures that:
115
117
  - Undefined routes (404 errors) are not tracked
116
118
  - Catch-all routes that handle unknown paths don't pollute the data
117
- - Only legitimate application routes are analyzed
118
- 3. **Request Tracking**: Each valid HTTP request is analyzed to extract route information
119
+ - Only legitimate application controller actions are analyzed
120
+ 3. **Request Tracking**: Each valid HTTP request is analyzed to extract controller and action information
119
121
  4. **Redis Storage**: Usage data is stored in Redis with the following structure:
120
- - Route path and HTTP method
122
+ - Controller name and action name
121
123
  - Access count within the timeframe
122
124
  - Last access timestamp
123
125
  5. **Data Expiration**: Redis keys automatically expire after the configured timeframe plus a buffer period
@@ -132,14 +134,36 @@ The middleware uses Rails' routing system to determine if a route is valid:
132
134
 
133
135
  This approach ensures that only routes you've intentionally defined are included in the usage analysis.
134
136
 
137
+ ## Controller Action Tracking
138
+
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:
140
+
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
144
+
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.
146
+
147
+ **Example Output:**
148
+ ```
149
+ COUNT CONTROLLER#ACTION METHOD LAST ACCESSED
150
+ --------------------------------------------------------------
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
154
+ ```
155
+
156
+ This grouping provides much more meaningful insights into which parts of your application are being used.
157
+
135
158
  ## Data Structure
136
159
 
137
- For each tracked route, the following data is stored in Redis:
160
+ For each tracked controller action, the following data is stored in Redis:
138
161
 
139
162
  ```ruby
140
163
  {
141
- route: "/users/:id", # Route pattern
142
- method: "GET", # HTTP method
164
+ controller: "users", # Controller name
165
+ action: "show", # Action name
166
+ method: "GET", # HTTP method (for reference)
143
167
  count: 15, # Number of accesses
144
168
  last_accessed: 1718721600 # Unix timestamp
145
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[:route], route_info[:method])
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 route and method info (in case it's the first time)
52
- redis.hset(redis_key, "route", route_info[:route])
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)
@@ -92,15 +93,12 @@ module Routes
92
93
  controller = env["action_controller.instance"]
93
94
  action = controller.action_name
94
95
  controller_name = controller.controller_name
95
-
96
- # Build route pattern from request path, removing query parameters
97
- route_path = request.path_info
96
+ method = request.request_method.upcase
98
97
 
99
98
  {
100
- route: route_path,
101
- method: request.request_method.upcase,
102
99
  controller: controller_name,
103
- action: action
100
+ action: action,
101
+ method: method
104
102
  }
105
103
  end
106
104
  rescue => e
@@ -108,8 +106,8 @@ module Routes
108
106
  nil
109
107
  end
110
108
 
111
- def build_redis_key(route, method)
112
- "#{configuration.redis_key_prefix}:#{method}:#{route.gsub('/', ':')}"
109
+ def build_redis_key(controller, action)
110
+ "#{configuration.redis_key_prefix}:#{controller}##{action}"
113
111
  end
114
112
 
115
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 route keys
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
- route: route_data["route"],
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 get_all_defined_routes
39
+ def get_all_defined_actions
39
40
  return [] unless defined?(Rails) && Rails.application
40
41
 
41
- Rails.application.routes.routes.map do |route|
42
- {
43
- route: route.path.spec.to_s.gsub(/\(\.:format\)$/, ""),
44
- method: route.verb,
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.compact.uniq { |r| [ r[:route], r[:method] ] }
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 merge_with_defined_routes(usage_stats)
53
- defined_routes = get_all_defined_routes
54
- usage_by_route_method = usage_stats.index_by { |stat| "#{stat[:method]}:#{stat[:route]}" }
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
- merged_routes = []
63
+ merged_actions = []
57
64
 
58
- defined_routes.each do |defined_route|
59
- route_key = "#{defined_route[:method]}:#{defined_route[:route]}"
60
- usage_stat = usage_by_route_method[route_key]
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
- merged_routes << defined_route.merge(usage_stat)
70
+ merged_actions << defined_action.merge(usage_stat)
64
71
  else
65
- merged_routes << defined_route.merge(
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 routes that aren't in the defined routes (in case of dynamic routes)
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
- route_key = "#{usage_stat[:method]}:#{usage_stat[:route]}"
75
- unless defined_routes.any? { |dr| "#{dr[:method]}:#{dr[:route]}" == route_key }
76
- merged_routes << usage_stat.merge(
77
- controller: nil,
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 route name
85
- merged_routes.sort_by { |route| [ -route[:count], route[:route] ] }
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,5 +1,5 @@
1
1
  module Routes
2
2
  module Analyzer
3
- VERSION = "0.1.2"
3
+ VERSION = "0.1.5"
4
4
  end
5
5
  end
@@ -1,60 +1,59 @@
1
1
  namespace :routes do
2
2
  namespace :analyzer do
3
- desc "Show routes usage statistics"
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 "Routes Usage Analysis (#{config.timeframe} days)"
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
- merged_routes = tracker.merge_with_defined_routes(usage_stats)
15
+ merged_actions = tracker.merge_with_defined_actions(usage_stats)
16
16
  else
17
- puts "Warning: Redis not configured. Showing defined routes only."
17
+ puts "Warning: Redis not configured. Showing defined actions only."
18
18
  puts
19
- merged_routes = tracker.get_all_defined_routes.map do |route|
20
- route.merge(count: 0, last_accessed: nil)
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 merged_routes.empty?
25
- puts "No routes found."
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 %-20s %s\n", "COUNT", "ROUTE", "METHOD", "CONTROLLER#ACTION", "LAST ACCESSED"
30
+ printf "%-8s %-40s %-15s %s\n", "COUNT", "CONTROLLER#ACTION", "METHOD", "LAST ACCESSED"
31
31
  puts "-" * 80
32
32
 
33
- merged_routes.each do |route|
34
- controller_action = if route[:controller] && route[:action]
35
- "#{route[:controller]}##{route[:action]}"
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 route[:last_accessed]
41
- route[:last_accessed].strftime("%Y-%m-%d %H:%M")
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 %-20s %s\n",
47
- route[:count],
48
- route[:route].to_s[0, 40],
49
- route[:method],
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 routes: #{merged_routes.count}"
56
- puts "Used routes: #{merged_routes.count { |r| r[:count] > 0 }}"
57
- puts "Unused routes: #{merged_routes.count { |r| r[:count] == 0 }}"
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 routes usage data"
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} route usage records."
81
+ puts "Cleared #{keys.count} controller action usage records."
83
82
  else
84
- puts "No route usage data found to clear."
83
+ puts "No controller action usage data found to clear."
85
84
  end
86
85
 
87
86
  rescue => e
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: routes-analyzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregorio Galante