Class: RailsAiBridge::PathResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_ai_bridge/path_resolver.rb

Overview

Resolves configured Rails logical paths to filesystem paths without leaking machine-specific absolute paths into generated assistant context.

Architectural role: intentional shared utility

PathResolver is deliberately used by every introspector that needs to locate files on disk (controller, model, view, stimulus, turbo, auth, api, config, action_text, activeStorage, nonArModels — 11 callers as of v3.6.0). It is NOT a god class despite high betweenness centrality in graph analyses: a foundational path-resolution utility is expected to sit at the centre of the introspector graph. Splitting it would spread path-safety logic (traversal guards, safe joins) across multiple files and weaken the single responsibility it currently holds.

The three private helper classes — SafeRelativePath, SafeJoin, and ConfiguredPathsError — are kept nested and private_constant so they cannot leak into the public API or be referenced by callers. This keeps the safety surface area small and auditable in one file.

If a future graph analysis flags this class again, check the betweenness centrality against the caller count before proposing a split: a utility with many callers and a single responsibility is healthy, not smelly.

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ PathResolver

Returns a new instance of PathResolver.

Parameters:

  • app (Rails::Application)

    host Rails application



135
136
137
138
# File 'lib/rails_ai_bridge/path_resolver.rb', line 135

def initialize(app)
  @app = app
  @root = app.root.to_s
end

Instance Method Details

#directories_for(logical_path) ⇒ Array<String>

Resolves directories for a logical Rails path.

Configured app.paths entries are preferred. When none are configured, the conventional root-relative directory is returned.

Parameters:

  • logical_path (String)

    Rails path key, such as "app/models"

Returns:

  • (Array<String>)

    absolute directory paths



147
148
149
150
151
152
# File 'lib/rails_ai_bridge/path_resolver.rb', line 147

def directories_for(logical_path)
  entries = configured_paths_for(logical_path)
  entries = [logical_path] if entries.empty?

  entries.map { |path| File.expand_path(path.to_s, @root) }.uniq
end

#existing_file_for(logical_path, relative_file) ⇒ String?

Finds the first existing file under a logical Rails path.

Parameters:

  • logical_path (String)

    Rails path key, such as "app/models"

  • relative_file (String)

    file path relative to the resolved directory

Returns:

  • (String, nil)

    absolute file path when found

Raises:

  • (ArgumentError)

    when relative_file is absolute or contains traversal segments



186
187
188
189
190
191
192
193
# File 'lib/rails_ai_bridge/path_resolver.rb', line 186

def existing_file_for(logical_path, relative_file)
  safe_file = SafeRelativePath.new(relative_file, argument_name: 'relative_file').to_s

  directories_for(logical_path).find do |path|
    candidate = SafeJoin.new(path, safe_file).to_s
    return candidate if File.exist?(candidate)
  end
end

#files_for(logical_path, extension:) ⇒ Array<String>

Finds files under every directory for a logical Rails path.

Parameters:

  • logical_path (String)

    Rails path key, such as "app/models"

  • extension (String)

    file extension without a leading dot

Returns:

  • (Array<String>)

    absolute file paths



159
160
161
# File 'lib/rails_ai_bridge/path_resolver.rb', line 159

def files_for(logical_path, extension:)
  glob_for(logical_path, "**/*.#{extension}")
end

#glob_for(logical_path, pattern) ⇒ Array<String>

Finds files matching a glob under every directory for a logical Rails path.

The pattern must be a safe relative glob. Absolute paths and traversal segments are rejected before Dir.glob runs.

Parameters:

  • logical_path (String)

    Rails path key, such as "app/views"

  • pattern (String)

    glob pattern relative to each resolved directory

Returns:

  • (Array<String>)

    absolute file paths

Raises:

  • (ArgumentError)

    when pattern is absolute or contains traversal segments



172
173
174
175
176
177
178
# File 'lib/rails_ai_bridge/path_resolver.rb', line 172

def glob_for(logical_path, pattern)
  safe_pattern = SafeRelativePath.new(pattern, argument_name: 'pattern').to_s

  directories_for(logical_path).flat_map do |path|
    Dir.exist?(path) ? Dir.glob(File.join(path, safe_pattern)) : []
  end
end

#logical_file_path(absolute_path, logical_path:) ⇒ String

Converts an absolute file path under a logical Rails path into a stable logical path for generated context.

Parameters:

  • absolute_path (String)

    filesystem path to map

  • logical_path (String)

    Rails path key, such as "app/models"

Returns:

  • (String)

    logical context path, root-relative path, or basename fallback



201
202
203
204
205
206
207
208
# File 'lib/rails_ai_bridge/path_resolver.rb', line 201

def logical_file_path(absolute_path, logical_path:)
  path = File.expand_path(absolute_path.to_s)
  matching_root = matching_root_for(path, logical_path)

  return logical_path_from_root(path, matching_root, logical_path) if matching_root

  relative_to_root(path)
end