Class: RailsAiBridge::PathResolver
- Inherits:
-
Object
- Object
- RailsAiBridge::PathResolver
- 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.2). 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
-
#directories_for(logical_path) ⇒ Array<String>
Resolves directories for a logical Rails path.
-
#existing_file_for(logical_path, relative_file) ⇒ String?
Finds the first existing file under a logical Rails path.
-
#files_for(logical_path, extension:) ⇒ Array<String>
Finds files under every directory for a logical Rails path.
-
#glob_for(logical_path, pattern) ⇒ Array<String>
Finds files matching a glob under every directory for a logical Rails path.
-
#initialize(app) ⇒ PathResolver
constructor
A new instance of PathResolver.
-
#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.
Constructor Details
#initialize(app) ⇒ PathResolver
Returns a new instance of PathResolver.
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.
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.(path.to_s, @root) }.uniq end |
#existing_file_for(logical_path, relative_file) ⇒ String?
Finds the first existing file under a logical Rails path.
Missing paths return nil without calling File.realpath. Existing
candidates are accepted only when their realpath stays inside the
resolved directory or the application root.
199 200 201 202 203 204 205 206 207 208 |
# File 'lib/rails_ai_bridge/path_resolver.rb', line 199 def existing_file_for(logical_path, relative_file) safe_file = SafeRelativePath.new(relative_file, argument_name: 'relative_file').to_s directories_for(logical_path).each do |path| candidate = SafeJoin.new(path, safe_file).to_s return candidate if contained_existing_path?(candidate, path) end nil end |
#files_for(logical_path, extension:) ⇒ Array<String>
Finds files under every directory for a logical Rails path.
Results are sorted then truncated to
Config::Introspection#max_files_per_path (default 2000).
162 163 164 |
# File 'lib/rails_ai_bridge/path_resolver.rb', line 162 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. Existing matches are kept
only when File.realpath stays inside the resolved directory or app root.
Matches are then sorted and truncated to
Config::Introspection#max_files_per_path (default 2000).
178 179 180 181 182 183 184 185 186 187 |
# File 'lib/rails_ai_bridge/path_resolver.rb', line 178 def glob_for(logical_path, pattern) safe_pattern = SafeRelativePath.new(pattern, argument_name: 'pattern').to_s matches = directories_for(logical_path).flat_map do |path| next [] unless Dir.exist?(path) Dir.glob(File.join(path, safe_pattern)).select { |file| contained_existing_path?(file, path) } end matches.sort.take(max_files_per_path) 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.
216 217 218 219 220 221 222 223 |
# File 'lib/rails_ai_bridge/path_resolver.rb', line 216 def logical_file_path(absolute_path, logical_path:) path = File.(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 |