Class: RailsAiBridge::Introspectors::NonArModelsIntrospector

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

Overview

Discovers concrete Ruby classes under +app/models+ that do not inherit from ActiveRecord::Base, so assistants can surface POJOs and service objects that would not appear in ActiveRecord model introspector.

After a best-effort eager load (including Zeitwerk +eager_load_dir+ for +app/models+ when enabled), entries are derived from +Object.const_source_location+ so anonymous or invalidly named classes are skipped.

Since:

  • 2.2.0

Constant Summary collapse

TAG =

Default label attached to each discovered non-AR class in tool and rules output.

Since:

  • 2.2.0

'POJO/Service'

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ NonArModelsIntrospector

Initializes the introspector for a Rails application. Stores the provided Rails application and its root path (as a string) for later introspection.

Examples:

Basic usage

introspector = NonArModelsIntrospector.new(Rails.application)
result = introspector.call

Parameters:

  • app (Rails::Application)

    host application whose +root+ and paths are used

  • app (Rails::Application)
    • The Rails application instance to inspect.

Since:

  • 2.2.0



25
26
27
28
# File 'lib/rails_ai_bridge/introspectors/non_ar_models_introspector.rb', line 25

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

Instance Method Details

#callHash

Builds the non-ActiveRecord model index for MCP and static rules.

Discovers concrete Ruby classes under app/models that do not inherit from ActiveRecord::Base.

The returned hash contains either a :non_ar_models array of discovered entries or an :error string when introspection fails. Each entry in :non_ar_models is a Hash with:

  • :name — the constant name of the class (String)
  • :relative_path — path to the source file relative to the app root (String)
  • :tag — the discovery tag, "POJO/Service" (String)

Examples:

Basic usage

introspector = NonArModelsIntrospector.new(Rails.application)
result = introspector.call

Returns:

  • (Hash)

    On success, a hash with key +:non_ar_models+ — an Array of hashes with keys +:name+ (String), +:relative_path+ (String from app root), and +:tag+ (String, usually the same value as NonArModelsIntrospector::TAG). On failure, a hash with key +:error+ and a String message.

  • (Hash)

    Either:

    • { non_ar_models: Array<Hash> } where each Hash has keys :name, :relative_path, and :tag, or
    • { error: String } containing a sanitized, truncated error message.

Since:

  • 2.2.0



50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/rails_ai_bridge/introspectors/non_ar_models_introspector.rb', line 50

def call
  eager_load!
  models_dir = File.join(@root, 'app', 'models')
  return { non_ar_models: [] } unless Dir.exist?(models_dir)

  models_root = File.expand_path(models_dir)
  entries = collect_entries(models_root)

  { non_ar_models: entries.values.sort_by { |h| h[:name] } }
rescue StandardError => error
  { error: sanitize_error_message(error.message) }
end

#sanitize_error_message(message) ⇒ String

Sanitizes error messages to prevent potential path disclosure Produces a sanitized, truncated error message safe for external exposure.

Examples:

Sanitizing an error with a file path

sanitize_error_message("Failed to load /path/to/secret/file")
# => "Failed to load /[REDACTED]"
sanitize_error_message("Very long error message that should be truncated...")

Parameters:

  • message (String)

    The original error message

  • message (String, nil)
    • The original error message which may contain file paths.

Returns:

  • (String)

    Sanitized error message safe for logging

  • (String)

    A message with filesystem paths replaced by "/[REDACTED]" and limited to 200 characters; returns "Introspection failed" if the input is nil or empty.

Since:

  • 2.2.0



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/rails_ai_bridge/introspectors/non_ar_models_introspector.rb', line 74

def sanitize_error_message(message)
  return 'Introspection failed' if message.blank?

  # Remove potential file paths that could expose directory structure
  sanitized = message.gsub(%r{/[^\s]*/[^/\s]+}, '/[REDACTED]')

  # Limit length to prevent log flooding and information disclosure
  if sanitized.length > 200
    "#{sanitized[0...197]}..."
  else
    sanitized
  end
end