Class: RailsAiContext::Introspectors::RouteIntrospector

Inherits:
Object
  • Object
show all
Extended by:
StaticTier
Defined in:
lib/rails_ai_context/introspectors/route_introspector.rb

Overview

Extracts route information from the Rails router including HTTP verb, path, controller#action, and route constraints.

Constant Summary collapse

MAX_DRAW_DEPTH =

A drawn file can draw again. Rails allows it; this stops a cycle of symlinked or mutually-drawing files from walking forever.

5

Constants included from StaticTier

StaticTier::ALTERNATE_SOURCE, StaticTier::FILES_ONLY, StaticTier::KINDS, StaticTier::RUNTIME_ONLY

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from StaticTier

answers_statically?, static_tier, static_tier_declared?, unavailable_reason, validate_static_tier!

Constructor Details

#initialize(app) ⇒ RouteIntrospector

Returns a new instance of RouteIntrospector.



17
18
19
# File 'lib/rails_ai_context/introspectors/route_introspector.rb', line 17

def initialize(app)
  @app = app
end

Instance Attribute Details

#appObject (readonly)

Returns the value of attribute app.



15
16
17
# File 'lib/rails_ai_context/introspectors/route_introspector.rb', line 15

def app
  @app
end

Instance Method Details

#callHash

Returns routes grouped by controller.

Returns:

  • (Hash)

    routes grouped by controller



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/rails_ai_context/introspectors/route_introspector.rb', line 22

def call
  routes = extract_routes
  root = routes.find { |r| r[:path] == "/" && r[:verb]&.include?("GET") }

  {
    # Merged, because that is how every surface that lists routes counts
    # them: Rails registers PATCH and PUT separately for one update
    # action, and a raw total here left the generated context files
    # quoting a grand total their own app-route number cannot reach.
    total_routes: Tools::BaseTool.dedupe_put_patch_routes(routes).size,
    by_controller: group_by_controller(routes),
    api_namespaces: detect_api_namespaces(routes),
    mounted_engines: detect_mounted_engines,
    # Everything routable that has no controller#action: Engine mounts
    # AND bare rack apps (propshaft's /assets mounts a Server instance,
    # which detect_mounted_engines' Class check can't see).
    unrouted_mounts: count_unrouted_mounts,
    root_route: root ? "#{root[:controller]}##{root[:action]}" : nil
  }
rescue => e
  { error: e.message }
end

#static_callObject

Static tier: answer route questions from config/routes.rb and the files it draws. Output mirrors the runtime shape exactly so tools, resources, and serializers need no static-awareness of their own. Routes behind dynamic constructs (devise_for, draw, concerns) are counted in :dynamic_routes rather than fabricated.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/rails_ai_context/introspectors/route_introspector.rb', line 51

def static_call
  routes_path = File.join(app.root.to_s, "config", "routes.rb")
  return { error: "config/routes.rb not found in #{app.root}" } unless File.exist?(routes_path)

  records, mounts, files = walk_routes_file(routes_path)
  entries = records.select { |r| r[:type] == :route }
  # A followed `draw` is no longer unexpanded - its routes are in the
  # list above. Counting it would overstate what is missing by exactly
  # the number of files this pass just read.
  dynamic = records.count { |r| r[:type] == :dynamic && !r[:followed] }

  result = {
    # Merged, for the reason `call` gives above: a raw total here made the
    # generated files say "8 total" where rails_get_routes, which merges
    # for itself, said 7 on the same `resources :posts`.
    total_routes: Tools::BaseTool.dedupe_put_patch_routes(entries).size,
    by_controller: group_by_controller(entries),
    api_namespaces: static_api_namespaces(entries),
    mounted_engines: mounts.map { |m| { engine: m[:engine], path: m[:path] } },
    root_route: static_root_route(entries),
    note: "Parsed statically from #{static_sources_phrase(files)} (app not booted)",
    confidence: Confidence::STATIC
  }
  result[:dynamic_routes] = dynamic if dynamic.positive?
  result
rescue => e
  { error: e.message }
end