Module: Woods::Extractors::RouteHelperResolver

Included in:
ControllerExtractor, MailerExtractor, PhlexExtractor, ViewComponentExtractor, ViewTemplateExtractor
Defined in:
lib/woods/extractors/route_helper_resolver.rb

Overview

Shared module for resolving named route helpers to controller#action targets.

Builds an inverse lookup from ‘Rails.application.routes.named_routes`, mapping route helper names (e.g., “new_post”) to their controller and action. Include this module and call #build_route_helper_map in your initializer.

Examples:

class MyExtractor
  include RouteHelperResolver

  def initialize
    build_route_helper_map
  end

  def find_target(source)
    resolve_route_helper("posts_path")
    #=> { controller: "PostsController", action: "index", path: "/posts", verb: "GET" }
  end
end

Constant Summary collapse

IGNORED_HELPER_PREFIXES =

Route helper prefixes that produce non-navigation dependencies. These generate asset URLs or are common false positives from non-route uses of _path/_url suffixes in Ruby code.

NOTE: ‘root` is intentionally excluded — root_path is the most common Rails route helper, but it appears so frequently in non-navigation contexts (path construction, config, tests) that it generates excessive noise. The tradeoff: “what links to the home page?” won’t appear in graph queries. Add new prefixes here when false positives are discovered in host apps.

%w[
  asset
  image
  stylesheet
  javascript
  font
  audio
  video
  turbo_stream
  file
  tmp
  base
  root
  log
  socket
  download
].freeze

Instance Method Summary collapse

Instance Method Details

#build_route_helper_mapObject

Build the route helper lookup map from Rails named routes. Call this once in your extractor’s initialize method.

Resilient to partial test doubles: any exception raised while traversing Rails routes (unstubbed ‘application` on a double, missing `named_routes`, etc.) is swallowed and leaves the map empty — extractors fall back to returning the helper name literal as the dependency target.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/woods/extractors/route_helper_resolver.rb', line 61

def build_route_helper_map
  @route_helper_map = {}
  return unless defined?(Rails)

  routes = safe_rails_application_routes
  return unless routes

  routes.named_routes.each do |name, route|
    controller = route.defaults[:controller]
    action = route.defaults[:action]
    next unless controller && action

    @route_helper_map[name.to_s] = {
      controller: "#{controller.camelize}Controller",
      action: action,
      path: route.path.spec.to_s.gsub('(.:format)', ''),
      verb: extract_route_verb(route)
    }
  end
rescue StandardError
  # Leave @route_helper_map empty — navigation-edge extractors will
  # fall back to the helper-name literal.
  @route_helper_map = {}
end

#resolve_route_helper(helper_name) ⇒ Hash?

Resolve a _path/_url helper to its controller#action target.

Parameters:

  • helper_name (String)

    e.g., “new_post_path”, “users_url”

Returns:

  • (Hash, nil)

    { controller:, action:, path:, verb: } or nil if unresolvable



106
107
108
109
110
111
# File 'lib/woods/extractors/route_helper_resolver.rb', line 106

def resolve_route_helper(helper_name)
  base = helper_name.sub(/_(path|url)\z/, '')
  return nil if IGNORED_HELPER_PREFIXES.any? { |prefix| base.start_with?("#{prefix}_") || base == prefix }

  @route_helper_map&.[](base)
end

#safe_rails_application_routesObject

True when Rails.application.routes is reachable. Probing via ‘respond_to?` first so partial RSpec doubles that haven’t stubbed ‘.application` don’t raise MockExpectationError (which descends from Exception, not StandardError — ‘rescue StandardError` would not catch it).



91
92
93
94
95
96
97
98
99
100
# File 'lib/woods/extractors/route_helper_resolver.rb', line 91

def safe_rails_application_routes
  return nil unless Rails.respond_to?(:application)

  app = Rails.application
  return nil unless app.respond_to?(:routes)

  app.routes
rescue StandardError
  nil
end