Module: RailsAiContext::SchemaAdapter

Defined in:
lib/rails_ai_context/schema_adapter.rb

Overview

One answer to "which database is this app on", for every surface that asks.

SchemaIntrospector writes static_parse when it read db/schema.rb instead of the connection. That is an internal detail: printed raw it names a database that does not exist. Four call sites each grew their own substitute and gave three different answers for one app - the generated CLAUDE.md said PostgreSQL, rails_get_schema said unknown, rails ai:inspect said static_parse. This is the seam they share.

Constant Summary collapse

PLACEHOLDERS =

Values that mean "no adapter was observed", not "the adapter is X".

%w[static_parse unknown].freeze
GEM_ADAPTERS =

Gemfile display names, only as the last resort.

{
  "pg" => "PostgreSQL",
  "mysql2" => "MySQL",
  "trilogy" => "MySQL",
  "sqlite3" => "SQLite"
}.freeze
DIALECTS =

structure.sql dialects, as SchemaIntrospector records them.

{
  "postgresql" => "PostgreSQL",
  "mysql" => "MySQL",
  "sqlite" => "SQLite"
}.freeze

Class Method Summary collapse

Class Method Details

.from_configurations(context) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/rails_ai_context/schema_adapter.rb', line 51

def from_configurations(context)
  multi = context[:multi_database]
  return nil unless Serializers::SectionGuard.usable?(multi)

  primary = Array(multi[:databases]).find { |db| db[:name].to_s == "primary" } ||
            Array(multi[:databases]).first
  adapter = primary&.dig(:adapter)
  return nil if placeholder?(adapter)

  # These are ActiveRecord adapter names (`postgresql`, `sqlite3`), not gem
  # names. The two overlap by accident at sqlite3/mysql2/trilogy, so
  # reading only GEM_ADAPTERS let `postgresql` fall through raw - a Postgres
  # app rendered "Database: postgresql" beside a SQLite app's "SQLite".
  DIALECTS[adapter.to_s] || GEM_ADAPTERS[adapter.to_s] || adapter
end

.from_dialect(schema) ⇒ Object



67
68
69
70
71
# File 'lib/rails_ai_context/schema_adapter.rb', line 67

def from_dialect(schema)
  return nil unless schema.is_a?(Hash)

  DIALECTS[schema[:dialect].to_s]
end

.from_gems(context) ⇒ Object

Last, and order-independent: an app with both pg and sqlite3 gets the same answer here as anywhere else, rather than depending on which match a loop happened to see last.



76
77
78
79
80
81
82
83
# File 'lib/rails_ai_context/schema_adapter.rb', line 76

def from_gems(context)
  gems = context[:gems]
  return nil unless Serializers::SectionGuard.usable?(gems)

  names = Array(gems[:notable_gems]).map { |g| g[:name].to_s }
  GEM_ADAPTERS.each_key { |gem_name| return GEM_ADAPTERS[gem_name] if names.include?(gem_name) }
  nil
end

.label(context) ⇒ String

Returns the adapter to show, never an internal placeholder.

Parameters:

  • context (Hash)

    a full introspection context

Returns:

  • (String)

    the adapter to show, never an internal placeholder



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/rails_ai_context/schema_adapter.rb', line 35

def label(context)
  context = {} unless context.is_a?(Hash)
  schema = context[:schema]
  observed = schema.is_a?(Hash) ? schema[:adapter] : nil
  return observed unless placeholder?(observed)

  # Best first: the app's own database config, which needs no connection.
  # Then the dialect parsed out of structure.sql. Only then the Gemfile,
  # which is a guess - an app can carry two adapter gems.
  from_configurations(context) || from_dialect(schema) || from_gems(context) || "unknown"
end

.placeholder?(adapter) ⇒ Boolean

Returns:

  • (Boolean)


47
48
49
# File 'lib/rails_ai_context/schema_adapter.rb', line 47

def placeholder?(adapter)
  adapter.nil? || PLACEHOLDERS.include?(adapter.to_s)
end