rails_map 1.1.0
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 +7 -0
- data/.idea/.gitignore +8 -0
- data/.idea/material_theme_project_new.xml +12 -0
- data/AUTHENTICATION.md +221 -0
- data/CHANGELOG.md +75 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/QUICKSTART.md +156 -0
- data/README.md +178 -0
- data/Rakefile +8 -0
- data/app/controllers/rails_map/docs_controller.rb +73 -0
- data/app/models/rails_map/user.rb +12 -0
- data/app/views/rails_map/docs/_styles.html.erb +489 -0
- data/app/views/rails_map/docs/controller.html.erb +137 -0
- data/app/views/rails_map/docs/index.html.erb +212 -0
- data/app/views/rails_map/docs/model.html.erb +214 -0
- data/app/views/rails_map/docs/routes.html.erb +139 -0
- data/config/initializers/rails_map.example.rb +44 -0
- data/config/routes.rb +9 -0
- data/docs/index.html +1354 -0
- data/lib/generators/rails_map/install_generator.rb +49 -0
- data/lib/generators/rails_map/templates/README +47 -0
- data/lib/generators/rails_map/templates/initializer.rb +38 -0
- data/lib/generators/rails_map/templates/migration.rb +14 -0
- data/lib/rails_map/auth.rb +15 -0
- data/lib/rails_map/configuration.rb +35 -0
- data/lib/rails_map/engine.rb +11 -0
- data/lib/rails_map/generators/html_generator.rb +120 -0
- data/lib/rails_map/parsers/model_parser.rb +257 -0
- data/lib/rails_map/parsers/route_parser.rb +356 -0
- data/lib/rails_map/railtie.rb +46 -0
- data/lib/rails_map/version.rb +5 -0
- data/lib/rails_map.rb +66 -0
- data/templates/controller.html.erb +74 -0
- data/templates/index.html.erb +64 -0
- data/templates/layout.html.erb +289 -0
- data/templates/model.html.erb +219 -0
- data/templates/routes.html.erb +86 -0
- metadata +144 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsMap
|
|
4
|
+
module Parsers
|
|
5
|
+
class RouteParser
|
|
6
|
+
RouteInfo = Struct.new(
|
|
7
|
+
:verb, :path, :controller, :action, :name, :constraints, :defaults, :base_path,
|
|
8
|
+
:path_params, :query_params, :request_body_params,
|
|
9
|
+
keyword_init: true
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
def parse
|
|
13
|
+
routes = collect_routes
|
|
14
|
+
Rails.logger.info "RouteParser: Collected #{routes.size} routes" if defined?(Rails.logger)
|
|
15
|
+
grouped = group_by_controller(routes)
|
|
16
|
+
Rails.logger.info "RouteParser: Grouped into #{grouped.size} controllers" if defined?(Rails.logger)
|
|
17
|
+
grouped
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def collect_routes
|
|
23
|
+
return [] unless defined?(Rails)
|
|
24
|
+
|
|
25
|
+
Rails.application.routes.routes.map do |route|
|
|
26
|
+
next if internal_route?(route)
|
|
27
|
+
|
|
28
|
+
requirements = route.requirements
|
|
29
|
+
controller = requirements[:controller]
|
|
30
|
+
action = requirements[:action]
|
|
31
|
+
|
|
32
|
+
next unless controller && action
|
|
33
|
+
|
|
34
|
+
path = extract_path(route)
|
|
35
|
+
path_params = extract_path_params(path)
|
|
36
|
+
|
|
37
|
+
RouteInfo.new(
|
|
38
|
+
verb: extract_verb(route),
|
|
39
|
+
path: path,
|
|
40
|
+
controller: controller,
|
|
41
|
+
action: action,
|
|
42
|
+
name: route.name,
|
|
43
|
+
constraints: extract_constraints(route),
|
|
44
|
+
defaults: extract_defaults(route),
|
|
45
|
+
base_path: extract_base_path(path),
|
|
46
|
+
path_params: path_params,
|
|
47
|
+
query_params: extract_query_params(controller, action),
|
|
48
|
+
request_body_params: extract_body_params(controller, action)
|
|
49
|
+
)
|
|
50
|
+
end.compact
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def extract_verb(route)
|
|
54
|
+
verb = route.verb
|
|
55
|
+
return verb if verb.is_a?(String)
|
|
56
|
+
return verb.source.gsub(/[$^]/, "") if verb.respond_to?(:source)
|
|
57
|
+
|
|
58
|
+
"ANY"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def extract_path(route)
|
|
62
|
+
path = route.path.spec.to_s
|
|
63
|
+
# Remove format segment if present
|
|
64
|
+
path.gsub("(.:format)", "")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def extract_path_params(path)
|
|
68
|
+
# Extract parameters from path like :id, :user_id, etc.
|
|
69
|
+
params = []
|
|
70
|
+
path.scan(/:(\w+)/).flatten.each do |param|
|
|
71
|
+
params << {
|
|
72
|
+
name: param,
|
|
73
|
+
type: infer_param_type(param),
|
|
74
|
+
required: true,
|
|
75
|
+
location: 'path'
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
params
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def extract_query_params(controller, action)
|
|
82
|
+
# Try to extract query parameters from controller action
|
|
83
|
+
params = []
|
|
84
|
+
begin
|
|
85
|
+
controller_file = find_controller_file(controller)
|
|
86
|
+
return params unless controller_file && File.exist?(controller_file)
|
|
87
|
+
|
|
88
|
+
content = File.read(controller_file)
|
|
89
|
+
|
|
90
|
+
# Look for params.permit or params.require patterns in the action
|
|
91
|
+
action_content = extract_action_content(content, action)
|
|
92
|
+
return params unless action_content
|
|
93
|
+
|
|
94
|
+
# Extract permitted parameters
|
|
95
|
+
permitted_params = extract_permitted_params(action_content)
|
|
96
|
+
|
|
97
|
+
# For GET requests, these are typically query params
|
|
98
|
+
permitted_params.each do |param|
|
|
99
|
+
params << {
|
|
100
|
+
name: param[:name],
|
|
101
|
+
type: param[:type] || 'string',
|
|
102
|
+
required: param[:required] || false,
|
|
103
|
+
location: 'query'
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
rescue => e
|
|
107
|
+
Rails.logger.debug "Could not extract query params for #{controller}##{action}: #{e.message}" if defined?(Rails.logger)
|
|
108
|
+
end
|
|
109
|
+
params
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def extract_body_params(controller, action)
|
|
113
|
+
# Try to extract request body parameters from controller action
|
|
114
|
+
params = []
|
|
115
|
+
begin
|
|
116
|
+
controller_file = find_controller_file(controller)
|
|
117
|
+
return params unless controller_file && File.exist?(controller_file)
|
|
118
|
+
|
|
119
|
+
content = File.read(controller_file)
|
|
120
|
+
|
|
121
|
+
# Look for params.permit or params.require patterns in the action
|
|
122
|
+
action_content = extract_action_content(content, action)
|
|
123
|
+
return params unless action_content
|
|
124
|
+
|
|
125
|
+
# Extract permitted parameters
|
|
126
|
+
permitted_params = extract_permitted_params(action_content)
|
|
127
|
+
|
|
128
|
+
# For POST/PUT/PATCH requests, these are typically body params
|
|
129
|
+
permitted_params.each do |param|
|
|
130
|
+
params << {
|
|
131
|
+
name: param[:name],
|
|
132
|
+
type: param[:type] || 'string',
|
|
133
|
+
required: param[:required] || false,
|
|
134
|
+
location: 'body'
|
|
135
|
+
}
|
|
136
|
+
end
|
|
137
|
+
rescue => e
|
|
138
|
+
Rails.logger.debug "Could not extract body params for #{controller}##{action}: #{e.message}" if defined?(Rails.logger)
|
|
139
|
+
end
|
|
140
|
+
params
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def find_controller_file(controller)
|
|
144
|
+
return nil unless defined?(Rails.root)
|
|
145
|
+
Rails.root.join('app', 'controllers', "#{controller}_controller.rb")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def extract_action_content(content, action)
|
|
149
|
+
# Extract the content of a specific action method
|
|
150
|
+
action_regex = /def\s+#{Regexp.escape(action)}\b.*?(?=\n\s*def\s|\n\s*private\s|\n\s*protected\s|\nend\s*\z)/m
|
|
151
|
+
match = content.match(action_regex)
|
|
152
|
+
match ? match[0] : nil
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def extract_permitted_params(action_content)
|
|
156
|
+
params = []
|
|
157
|
+
|
|
158
|
+
# Pattern 1: params.require(:model).permit(:attr1, :attr2, ...)
|
|
159
|
+
require_permit_pattern = /params\.require\(:(\w+)\)\.permit\((.*?)\)/m
|
|
160
|
+
if match = action_content.match(require_permit_pattern)
|
|
161
|
+
model_name = match[1]
|
|
162
|
+
permitted_attrs = match[2]
|
|
163
|
+
|
|
164
|
+
# Extract individual attributes
|
|
165
|
+
permitted_attrs.scan(/:(\w+)/).flatten.each do |attr|
|
|
166
|
+
params << {
|
|
167
|
+
name: "#{model_name}[#{attr}]",
|
|
168
|
+
type: infer_param_type(attr),
|
|
169
|
+
required: true
|
|
170
|
+
}
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Pattern 2: params.permit(:attr1, :attr2, ...)
|
|
175
|
+
permit_pattern = /params\.permit\((.*?)\)/m
|
|
176
|
+
action_content.scan(permit_pattern).flatten.each do |permitted_attrs|
|
|
177
|
+
permitted_attrs.scan(/:(\w+)/).flatten.each do |attr|
|
|
178
|
+
# Avoid duplicates
|
|
179
|
+
unless params.any? { |p| p[:name].include?(attr) }
|
|
180
|
+
params << {
|
|
181
|
+
name: attr,
|
|
182
|
+
type: infer_param_type(attr),
|
|
183
|
+
required: false
|
|
184
|
+
}
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Pattern 3: params[:key] or params['key']
|
|
190
|
+
params_access_pattern = /params\[['"]?:?(\w+)['"]?\]/
|
|
191
|
+
action_content.scan(params_access_pattern).flatten.uniq.each do |attr|
|
|
192
|
+
# Avoid duplicates and common Rails params
|
|
193
|
+
next if %w[controller action format id].include?(attr)
|
|
194
|
+
unless params.any? { |p| p[:name] == attr || p[:name].include?(attr) }
|
|
195
|
+
params << {
|
|
196
|
+
name: attr,
|
|
197
|
+
type: infer_param_type(attr),
|
|
198
|
+
required: false
|
|
199
|
+
}
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
params.uniq { |p| p[:name] }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def infer_param_type(param_name)
|
|
207
|
+
# Infer parameter type from name
|
|
208
|
+
case param_name.to_s
|
|
209
|
+
when /_(id|ids)$/
|
|
210
|
+
'integer'
|
|
211
|
+
when /^is_/, /^has_/, /_flag$/, /_enabled$/
|
|
212
|
+
'boolean'
|
|
213
|
+
when /_at$/, /_date$/
|
|
214
|
+
'datetime'
|
|
215
|
+
when /_count$/, /_number$/, /^count_/, /^num_/
|
|
216
|
+
'integer'
|
|
217
|
+
when /_price$/, /_amount$/, /_total$/
|
|
218
|
+
'decimal'
|
|
219
|
+
when /_email$/
|
|
220
|
+
'email'
|
|
221
|
+
when /_url$/
|
|
222
|
+
'url'
|
|
223
|
+
else
|
|
224
|
+
'string'
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def extract_constraints(route)
|
|
229
|
+
constraints = {}
|
|
230
|
+
route.requirements.each do |key, value|
|
|
231
|
+
next if %i[controller action].include?(key)
|
|
232
|
+
|
|
233
|
+
constraints[key] = value.is_a?(Regexp) ? value.source : value.to_s
|
|
234
|
+
end
|
|
235
|
+
constraints
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def extract_defaults(route)
|
|
239
|
+
defaults = {}
|
|
240
|
+
route.defaults.each do |key, value|
|
|
241
|
+
next if %i[controller action].include?(key)
|
|
242
|
+
|
|
243
|
+
defaults[key] = value.to_s
|
|
244
|
+
end
|
|
245
|
+
defaults
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def group_by_controller(routes)
|
|
249
|
+
grouped = routes.group_by(&:controller)
|
|
250
|
+
|
|
251
|
+
grouped.transform_values do |controller_routes|
|
|
252
|
+
sorted_routes = controller_routes.sort_by { |r| [r.path, verb_order(r.verb)] }
|
|
253
|
+
{
|
|
254
|
+
routes: sorted_routes,
|
|
255
|
+
actions: controller_routes.map(&:action).uniq.sort,
|
|
256
|
+
base_path: find_common_base_path(controller_routes)
|
|
257
|
+
}
|
|
258
|
+
end.sort_by { |controller, _| controller.downcase }.to_h
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def extract_base_path(path)
|
|
262
|
+
# Extract first meaningful segment: /users/:id -> /users
|
|
263
|
+
segments = path.split('/').reject(&:empty?)
|
|
264
|
+
return '/' if segments.empty?
|
|
265
|
+
|
|
266
|
+
# Take first segment that doesn't start with : or *
|
|
267
|
+
base = segments.take_while { |s| !s.start_with?(':', '*') }
|
|
268
|
+
base.empty? ? "/#{segments.first}" : "/#{base.join('/')}"
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def find_common_base_path(routes)
|
|
272
|
+
paths = routes.map(&:base_path).uniq
|
|
273
|
+
return paths.first if paths.size == 1
|
|
274
|
+
|
|
275
|
+
# Find common prefix
|
|
276
|
+
paths.min_by(&:length) || '/'
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def verb_order(verb)
|
|
280
|
+
# Order: GET, POST, PUT, PATCH, DELETE, others
|
|
281
|
+
order = { 'GET' => 0, 'POST' => 1, 'PUT' => 2, 'PATCH' => 3, 'DELETE' => 4 }
|
|
282
|
+
order[verb.to_s.upcase] || 5
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def internal_route?(route)
|
|
286
|
+
# Rails 7+ uses internal? method
|
|
287
|
+
return true if route.respond_to?(:internal?) && route.internal?
|
|
288
|
+
|
|
289
|
+
# Rails 6.x uses internal attribute
|
|
290
|
+
return true if route.respond_to?(:internal) && route.internal
|
|
291
|
+
|
|
292
|
+
# Check if it's a Rails internal route by path
|
|
293
|
+
path = route.path.spec.to_s rescue ''
|
|
294
|
+
return true if path.start_with?('/rails/')
|
|
295
|
+
|
|
296
|
+
# Check controller namespace - exclude gem/engine controllers
|
|
297
|
+
controller = route.requirements[:controller].to_s
|
|
298
|
+
return true if excluded_controller?(controller)
|
|
299
|
+
|
|
300
|
+
false
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def excluded_controller?(controller)
|
|
304
|
+
return true if controller.nil? || controller.empty?
|
|
305
|
+
|
|
306
|
+
controller_downcase = controller.to_s.downcase
|
|
307
|
+
|
|
308
|
+
# Exclude common gem/internal controller namespaces (check first segment)
|
|
309
|
+
excluded_prefixes = %w[
|
|
310
|
+
rails_map
|
|
311
|
+
action_mailbox
|
|
312
|
+
action_cable
|
|
313
|
+
active_storage
|
|
314
|
+
action_text
|
|
315
|
+
turbo
|
|
316
|
+
devise
|
|
317
|
+
sidekiq
|
|
318
|
+
letter_opener
|
|
319
|
+
better_errors
|
|
320
|
+
web_console
|
|
321
|
+
solid_queue
|
|
322
|
+
solid_cache
|
|
323
|
+
mission_control
|
|
324
|
+
rails
|
|
325
|
+
graphiql
|
|
326
|
+
pghero
|
|
327
|
+
blazer
|
|
328
|
+
flipper
|
|
329
|
+
rswag
|
|
330
|
+
swagger
|
|
331
|
+
avo
|
|
332
|
+
administrate
|
|
333
|
+
rails_admin
|
|
334
|
+
good_job
|
|
335
|
+
que
|
|
336
|
+
delayed
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
# Check if controller starts with any excluded prefix
|
|
340
|
+
first_segment = controller_downcase.split('/').first
|
|
341
|
+
return true if excluded_prefixes.include?(first_segment)
|
|
342
|
+
|
|
343
|
+
# Check prefix match (e.g., "action_mailbox/ingresses/...")
|
|
344
|
+
return true if excluded_prefixes.any? { |prefix| controller_downcase.start_with?("#{prefix}/") }
|
|
345
|
+
|
|
346
|
+
# Check if controller file exists in app/controllers of host app
|
|
347
|
+
if defined?(Rails.root)
|
|
348
|
+
controller_file = Rails.root.join('app', 'controllers', "#{controller}_controller.rb")
|
|
349
|
+
return true unless File.exist?(controller_file)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
false
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
|
|
5
|
+
module RailsMap
|
|
6
|
+
class Railtie < Rails::Railtie
|
|
7
|
+
railtie_name :rails_map
|
|
8
|
+
|
|
9
|
+
rake_tasks do
|
|
10
|
+
namespace :doc do
|
|
11
|
+
desc "Generate HTML documentation for routes and models"
|
|
12
|
+
task generate: :environment do
|
|
13
|
+
puts "Generating documentation..."
|
|
14
|
+
RailsMap.generate
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
desc "Generate documentation and open in browser"
|
|
18
|
+
task open: :generate do
|
|
19
|
+
index_path = File.join(RailsMap.configuration.output_dir, "index.html")
|
|
20
|
+
|
|
21
|
+
if File.exist?(index_path)
|
|
22
|
+
system("open", index_path) || system("xdg-open", index_path) || system("start", index_path)
|
|
23
|
+
else
|
|
24
|
+
puts "Documentation not found at #{index_path}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
desc "Clean generated documentation"
|
|
29
|
+
task clean: :environment do
|
|
30
|
+
output_dir = RailsMap.configuration.output_dir
|
|
31
|
+
if Dir.exist?(output_dir)
|
|
32
|
+
FileUtils.rm_rf(output_dir)
|
|
33
|
+
puts "Removed documentation at #{output_dir}"
|
|
34
|
+
else
|
|
35
|
+
puts "No documentation found at #{output_dir}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Allow configuration via Rails initializer
|
|
42
|
+
initializer "rails_map.configure" do
|
|
43
|
+
# Configuration can be done in config/initializers/rails_map.rb
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/rails_map.rb
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "rails_map/version"
|
|
4
|
+
require_relative "rails_map/configuration"
|
|
5
|
+
require_relative "rails_map/auth"
|
|
6
|
+
require_relative "rails_map/parsers/route_parser"
|
|
7
|
+
require_relative "rails_map/parsers/model_parser"
|
|
8
|
+
require_relative "rails_map/generators/html_generator"
|
|
9
|
+
|
|
10
|
+
module RailsMap
|
|
11
|
+
class Error < StandardError; end
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
attr_writer :configuration
|
|
15
|
+
|
|
16
|
+
def configuration
|
|
17
|
+
@configuration ||= Configuration.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def configure
|
|
21
|
+
yield(configuration)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def generate
|
|
25
|
+
Generator.new(configuration).generate
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class Generator
|
|
30
|
+
attr_reader :config
|
|
31
|
+
|
|
32
|
+
def initialize(config)
|
|
33
|
+
@config = config
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def generate
|
|
37
|
+
ensure_output_directory
|
|
38
|
+
|
|
39
|
+
routes_data = Parsers::RouteParser.new.parse
|
|
40
|
+
models_data = Parsers::ModelParser.new.parse
|
|
41
|
+
|
|
42
|
+
generator = Generators::HtmlGenerator.new(
|
|
43
|
+
routes: routes_data,
|
|
44
|
+
models: models_data,
|
|
45
|
+
output_dir: config.output_dir
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
generator.generate_all
|
|
49
|
+
|
|
50
|
+
puts "Documentation generated successfully in #{config.output_dir}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def ensure_output_directory
|
|
56
|
+
FileUtils.mkdir_p(config.output_dir)
|
|
57
|
+
FileUtils.mkdir_p(File.join(config.output_dir, "controllers"))
|
|
58
|
+
FileUtils.mkdir_p(File.join(config.output_dir, "models"))
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if defined?(Rails::Railtie)
|
|
64
|
+
require_relative "rails_map/engine"
|
|
65
|
+
require_relative "rails_map/railtie"
|
|
66
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<div class="breadcrumb">
|
|
2
|
+
<a href="../index.html">Home</a>
|
|
3
|
+
<span>/</span>
|
|
4
|
+
<a href="../routes.html">Routes</a>
|
|
5
|
+
<span>/</span>
|
|
6
|
+
<span><%= controller_name.camelize %>Controller</span>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="card">
|
|
10
|
+
<div class="card-header">
|
|
11
|
+
<h2 class="card-title"><%= controller_name.camelize %>Controller</h2>
|
|
12
|
+
<span class="badge badge-any"><%= routes.size %> routes</span>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div style="display: flex; gap: 2rem; margin-bottom: 1.5rem; flex-wrap: wrap;">
|
|
16
|
+
<div>
|
|
17
|
+
<strong>Base Path:</strong><br>
|
|
18
|
+
<code style="background: var(--primary-color); color: white; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: 1rem;"><%= base_path || '/' %></code>
|
|
19
|
+
</div>
|
|
20
|
+
<div>
|
|
21
|
+
<strong>Actions:</strong><br>
|
|
22
|
+
<% actions.each_with_index do |action, index| %>
|
|
23
|
+
<code><%= action %></code><%= index < actions.size - 1 ? ' ' : '' %>
|
|
24
|
+
<% end %>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<% if routes.any? %>
|
|
29
|
+
<div class="table-container">
|
|
30
|
+
<table>
|
|
31
|
+
<thead>
|
|
32
|
+
<tr>
|
|
33
|
+
<th>Method</th>
|
|
34
|
+
<th>Path</th>
|
|
35
|
+
<th>Action</th>
|
|
36
|
+
<th>Route Name</th>
|
|
37
|
+
<th>Constraints</th>
|
|
38
|
+
</tr>
|
|
39
|
+
</thead>
|
|
40
|
+
<tbody>
|
|
41
|
+
<% routes.each do |route| %>
|
|
42
|
+
<tr>
|
|
43
|
+
<td>
|
|
44
|
+
<span class="badge badge-<%= route.verb.downcase.split('|').first %>">
|
|
45
|
+
<%= route.verb %>
|
|
46
|
+
</span>
|
|
47
|
+
</td>
|
|
48
|
+
<td><code><%= route.path %></code></td>
|
|
49
|
+
<td><code><%= route.action %></code></td>
|
|
50
|
+
<td>
|
|
51
|
+
<% if route.name %>
|
|
52
|
+
<code><%= route.name %></code>
|
|
53
|
+
<% else %>
|
|
54
|
+
<span style="color: var(--text-muted);">—</span>
|
|
55
|
+
<% end %>
|
|
56
|
+
</td>
|
|
57
|
+
<td>
|
|
58
|
+
<% if route.constraints.any? %>
|
|
59
|
+
<% route.constraints.each do |key, value| %>
|
|
60
|
+
<code><%= key %>: <%= value %></code><br>
|
|
61
|
+
<% end %>
|
|
62
|
+
<% else %>
|
|
63
|
+
<span style="color: var(--text-muted);">—</span>
|
|
64
|
+
<% end %>
|
|
65
|
+
</td>
|
|
66
|
+
</tr>
|
|
67
|
+
<% end %>
|
|
68
|
+
</tbody>
|
|
69
|
+
</table>
|
|
70
|
+
</div>
|
|
71
|
+
<% else %>
|
|
72
|
+
<div class="empty-state">No routes found for this controller</div>
|
|
73
|
+
<% end %>
|
|
74
|
+
</div>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<div class="stats">
|
|
2
|
+
<div class="stat">
|
|
3
|
+
<div class="stat-value"><%= controllers_count %></div>
|
|
4
|
+
<div class="stat-label">Controllers</div>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="stat">
|
|
7
|
+
<div class="stat-value"><%= routes_count %></div>
|
|
8
|
+
<div class="stat-label">Routes</div>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="stat">
|
|
11
|
+
<div class="stat-value"><%= models_count %></div>
|
|
12
|
+
<div class="stat-label">Models</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="nav">
|
|
17
|
+
<a href="routes.html">All Routes</a>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<h2 class="section-header">Controllers</h2>
|
|
21
|
+
<% if controllers.any? %>
|
|
22
|
+
<div class="grid">
|
|
23
|
+
<% controllers.each do |controller, data| %>
|
|
24
|
+
<div class="card">
|
|
25
|
+
<div class="card-header">
|
|
26
|
+
<h3 class="card-title"><%= controller.camelize %>Controller</h3>
|
|
27
|
+
<span class="badge badge-any"><%= data[:routes].size %> routes</span>
|
|
28
|
+
</div>
|
|
29
|
+
<p style="margin-bottom: 0.5rem;">
|
|
30
|
+
<code style="background: var(--primary-color); color: white; padding: 0.25rem 0.5rem; border-radius: 0.25rem;"><%= data[:base_path] || '/' %></code>
|
|
31
|
+
</p>
|
|
32
|
+
<p style="color: var(--text-muted); margin-bottom: 1rem; font-size: 0.875rem;">
|
|
33
|
+
Actions: <%= data[:actions].join(', ') %>
|
|
34
|
+
</p>
|
|
35
|
+
<a href="controllers/<%= controller.gsub('/', '_') %>.html" class="link">View Details →</a>
|
|
36
|
+
</div>
|
|
37
|
+
<% end %>
|
|
38
|
+
</div>
|
|
39
|
+
<% else %>
|
|
40
|
+
<div class="empty-state">No controllers found</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
|
|
43
|
+
<h2 class="section-header">Models</h2>
|
|
44
|
+
<% if models.any? %>
|
|
45
|
+
<div class="grid">
|
|
46
|
+
<% models.each do |name, model| %>
|
|
47
|
+
<div class="card">
|
|
48
|
+
<div class="card-header">
|
|
49
|
+
<h3 class="card-title"><%= name %></h3>
|
|
50
|
+
<span class="badge badge-any"><%= model.columns.size %> columns</span>
|
|
51
|
+
</div>
|
|
52
|
+
<p style="color: var(--text-muted); margin-bottom: 0.5rem;">
|
|
53
|
+
Table: <code><%= model.table_name || 'N/A' %></code>
|
|
54
|
+
</p>
|
|
55
|
+
<p style="color: var(--text-muted); margin-bottom: 1rem;">
|
|
56
|
+
<%= model.associations.size %> associations
|
|
57
|
+
</p>
|
|
58
|
+
<a href="models/<%= name.underscore.gsub('/', '_') %>.html" class="link">View Details →</a>
|
|
59
|
+
</div>
|
|
60
|
+
<% end %>
|
|
61
|
+
</div>
|
|
62
|
+
<% else %>
|
|
63
|
+
<div class="empty-state">No models found</div>
|
|
64
|
+
<% end %>
|