rails-ai-context 5.35.0 → 5.35.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1024 -0
- data/CONTEXT.md +37 -7
- data/CONTRIBUTING.md +30 -1
- data/README.md +9 -9
- data/SECURITY.md +2 -2
- data/docs/ARCHITECTURE.md +12 -4
- data/docs/CLI.md +44 -15
- data/docs/COMPATIBILITY.md +34 -8
- data/docs/CONFIGURATION.md +28 -5
- data/docs/CUSTOM_TOOLS.md +8 -2
- data/docs/FAQ.md +5 -5
- data/docs/GUIDE.md +33 -65
- data/docs/INTROSPECTORS.md +4 -4
- data/docs/QUICKSTART.md +1 -1
- data/docs/RECIPES.md +5 -5
- data/docs/RELEASING.md +3 -1
- data/docs/SECURITY.md +255 -74
- data/docs/SETUP.md +1 -1
- data/docs/STANDALONE.md +2 -2
- data/docs/TOOLS.md +151 -90
- data/docs/TROUBLESHOOTING.md +123 -8
- data/docs/_config.yml +1 -1
- data/docs/adr/0008-query-runs-only-where-it-can-guard.md +38 -0
- data/docs/adr/0009-dbhub-shapes-in-ruby.md +26 -0
- data/docs/adr/0010-one-config-tool-with-sections.md +32 -0
- data/docs/index.md +2 -2
- data/exe/rails-ai-context +24 -9
- data/lib/generators/rails_ai_context/install/install_generator.rb +2 -0
- data/lib/rails_ai_context/app_databases.rb +377 -0
- data/lib/rails_ai_context/app_kind.rb +2 -0
- data/lib/rails_ai_context/cli/app_root.rb +1 -0
- data/lib/rails_ai_context/cli/tool_runner.rb +110 -99
- data/lib/rails_ai_context/configuration.rb +15 -8
- data/lib/rails_ai_context/database_family/mysql_runtime.rb +170 -0
- data/lib/rails_ai_context/database_family/postgresql_plan.rb +124 -0
- data/lib/rails_ai_context/database_family/postgresql_runtime.rb +211 -0
- data/lib/rails_ai_context/database_family/sqlite_runtime.rb +391 -0
- data/lib/rails_ai_context/database_family.rb +282 -0
- data/lib/rails_ai_context/database_file.rb +61 -7
- data/lib/rails_ai_context/database_yml.rb +7 -29
- data/lib/rails_ai_context/doctor.rb +15 -16
- data/lib/rails_ai_context/instrumentation.rb +1 -1
- data/lib/rails_ai_context/introspectors/connection_pool_introspector.rb +8 -23
- data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +6 -70
- data/lib/rails_ai_context/introspectors/declared_constant.rb +19 -7
- data/lib/rails_ai_context/introspectors/env_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/env_references.rb +1 -1
- data/lib/rails_ai_context/introspectors/job_introspector.rb +7 -7
- data/lib/rails_ai_context/introspectors/migration_introspector.rb +8 -11
- data/lib/rails_ai_context/introspectors/migration_replay/statements.rb +9 -9
- data/lib/rails_ai_context/introspectors/model_introspector.rb +33 -39
- data/lib/rails_ai_context/introspectors/multi_database_introspector.rb +33 -30
- data/lib/rails_ai_context/introspectors/node_source.rb +2 -0
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +80 -24
- data/lib/rails_ai_context/introspectors/pg_partitions.rb +1 -4
- data/lib/rails_ai_context/introspectors/schema_conventions.rb +6 -8
- data/lib/rails_ai_context/introspectors/schema_dump_path.rb +12 -14
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +47 -27
- data/lib/rails_ai_context/introspectors/schema_reader.rb +1 -1
- data/lib/rails_ai_context/introspectors/source_scan.rb +39 -4
- data/lib/rails_ai_context/migration_status.rb +18 -46
- data/lib/rails_ai_context/payload.rb +48 -1
- data/lib/rails_ai_context/pending_migrations.rb +3 -4
- data/lib/rails_ai_context/run_cache.rb +5 -0
- data/lib/rails_ai_context/schema_adapter.rb +13 -18
- data/lib/rails_ai_context/secret_columns.rb +506 -0
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +2 -2
- data/lib/rails_ai_context/serializers/markdown_serializer.rb +14 -10
- data/lib/rails_ai_context/serializers/section_facts.rb +12 -2
- data/lib/rails_ai_context/serializers/stack_overview_helper.rb +5 -2
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +35 -7
- data/lib/rails_ai_context/server.rb +143 -29
- data/lib/rails_ai_context/sql_gate.rb +1007 -0
- data/lib/rails_ai_context/tasks/rails_ai_context.rake +1 -2
- data/lib/rails_ai_context/test_helper.rb +3 -1
- data/lib/rails_ai_context/tools/analyze_feature.rb +11 -7
- data/lib/rails_ai_context/tools/base_tool.rb +71 -48
- data/lib/rails_ai_context/tools/dependency_graph.rb +1 -1
- data/lib/rails_ai_context/tools/diagnose.rb +105 -42
- data/lib/rails_ai_context/tools/generate_test.rb +4 -6
- data/lib/rails_ai_context/tools/get_active_support.rb +1 -6
- data/lib/rails_ai_context/tools/get_api.rb +1 -6
- data/lib/rails_ai_context/tools/get_autoload.rb +1 -6
- data/lib/rails_ai_context/tools/get_callbacks.rb +5 -5
- data/lib/rails_ai_context/tools/get_component_catalog.rb +6 -7
- data/lib/rails_ai_context/tools/get_concern.rb +6 -6
- data/lib/rails_ai_context/tools/get_config.rb +189 -12
- data/lib/rails_ai_context/tools/get_context.rb +111 -105
- data/lib/rails_ai_context/tools/get_controllers.rb +8 -5
- data/lib/rails_ai_context/tools/get_conventions.rb +1 -6
- data/lib/rails_ai_context/tools/get_engines.rb +1 -6
- data/lib/rails_ai_context/tools/get_env.rb +1 -6
- data/lib/rails_ai_context/tools/get_env_config.rb +2 -8
- data/lib/rails_ai_context/tools/get_frontend_stack.rb +1 -6
- data/lib/rails_ai_context/tools/get_gems.rb +1 -6
- data/lib/rails_ai_context/tools/get_helper_methods.rb +4 -5
- data/lib/rails_ai_context/tools/get_i18n.rb +1 -7
- data/lib/rails_ai_context/tools/get_job_pattern.rb +1 -1
- data/lib/rails_ai_context/tools/get_mailers.rb +1 -1
- data/lib/rails_ai_context/tools/get_model_details.rb +5 -8
- data/lib/rails_ai_context/tools/get_partial_interface.rb +3 -4
- data/lib/rails_ai_context/tools/get_schema.rb +10 -8
- data/lib/rails_ai_context/tools/get_service_pattern.rb +60 -24
- data/lib/rails_ai_context/tools/get_turbo_map.rb +1 -1
- data/lib/rails_ai_context/tools/migration_advisor.rb +47 -32
- data/lib/rails_ai_context/tools/onboard.rb +24 -24
- data/lib/rails_ai_context/tools/performance_check.rb +15 -2
- data/lib/rails_ai_context/tools/query.rb +142 -1356
- data/lib/rails_ai_context/tools/read_logs.rb +1 -1
- data/lib/rails_ai_context/tools/review_changes.rb +6 -7
- data/lib/rails_ai_context/tools/runtime_info.rb +125 -88
- data/lib/rails_ai_context/tools/safe_call.rb +70 -28
- data/lib/rails_ai_context/tools/search_code.rb +0 -4
- data/lib/rails_ai_context/tools/search_docs.rb +3 -1
- data/lib/rails_ai_context/tools/security_scan.rb +9 -5
- data/lib/rails_ai_context/tools/session_context.rb +18 -12
- data/lib/rails_ai_context/tools/validate.rb +1 -0
- data/lib/rails_ai_context/tools/validate_semantics.rb +5 -5
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context.rb +4 -7
- data/server.json +2 -2
- metadata +14 -3
|
@@ -54,7 +54,7 @@ module RailsAiContext
|
|
|
54
54
|
if MigrationReplay.migration_files(migrate_dirs, root: root).any?
|
|
55
55
|
pk_type = SchemaConventions.implicit_pk_type(root)
|
|
56
56
|
return from_tables(MigrationReplay.tables(migrate_dirs, pk_type: pk_type, root: root,
|
|
57
|
-
adapter: SchemaConventions.database_adapter_for(root,
|
|
57
|
+
adapter: SchemaConventions.database_adapter_for(root, nil)),
|
|
58
58
|
source: :migrations, path: migrate_dirs.first)
|
|
59
59
|
end
|
|
60
60
|
|
|
@@ -168,12 +168,14 @@ module RailsAiContext
|
|
|
168
168
|
declared = Set.new
|
|
169
169
|
ignored = PathResolver.ignored_dirs(root).map { |dir| PathResolver.root_key(dir) }
|
|
170
170
|
found = PathResolver.extra_model_roots(root).each_with_object([]) do |dir, records|
|
|
171
|
-
scan_dir(dir, root, real_root,
|
|
171
|
+
scan_dir(dir, root, real_root, false) do |record|
|
|
172
172
|
next unless seen.add?(record.path)
|
|
173
173
|
next if ignored.any? { |ignored_dir| SafePath.contained?(record.path, ignored_dir) }
|
|
174
174
|
|
|
175
|
+
source = SafeFile.read(record.path).to_s
|
|
176
|
+
record = record.with(path_name: concerns_name(record.path_name, source))
|
|
175
177
|
declared << record.path_name
|
|
176
|
-
pairs = class_declarations(
|
|
178
|
+
pairs = class_declarations(source)
|
|
177
179
|
next if pairs.empty?
|
|
178
180
|
|
|
179
181
|
records << [ record, pairs ]
|
|
@@ -183,6 +185,29 @@ module RailsAiContext
|
|
|
183
185
|
[ found, declared ]
|
|
184
186
|
end
|
|
185
187
|
|
|
188
|
+
# A concerns/ under an app/ root is an autoload root of its own, so its files carry no
|
|
189
|
+
# `Concerns::`; under a root the app adds it is a namespace, which the file then declares.
|
|
190
|
+
def concerns_name(path_name, source)
|
|
191
|
+
return path_name unless path_name.start_with?("Concerns::")
|
|
192
|
+
|
|
193
|
+
declared = DeclaredConstant.declared_names(source) + DeclaredConstant.declared_module_names(source)
|
|
194
|
+
declared.any? { |name| name.split("::").include?("Concerns") } ? path_name : path_name.delete_prefix("Concerns::")
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# The class a file under a concerns/ is named for; an inner class of a mixin is not it.
|
|
198
|
+
def concern_class(source, path_name)
|
|
199
|
+
DeclaredConstant.declaration_named(DeclaredConstant.declarations(source, path_name: path_name), path_name)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Whether a concerns file's own class names a model file's class or a record base as its superclass.
|
|
203
|
+
def concern_model?(source, path_name, model_names, base_model)
|
|
204
|
+
declaration = concern_class(source, path_name)
|
|
205
|
+
return false unless declaration&.superclass
|
|
206
|
+
|
|
207
|
+
candidates = declaration.nesting.map { |scope| "#{scope}::#{declaration.superclass}" } << declaration.superclass
|
|
208
|
+
candidates.any? { |candidate| model_names.include?(candidate.downcase) } || (base_model ? base_model.call(candidates) : false)
|
|
209
|
+
end
|
|
210
|
+
|
|
186
211
|
# [name, superclass] of each `class X < Y` that starts its line. A `^` anchor
|
|
187
212
|
# makes Onigmo try every offset, ten times slower over a service tree.
|
|
188
213
|
def class_declarations(source)
|
|
@@ -195,7 +220,7 @@ module RailsAiContext
|
|
|
195
220
|
pairs
|
|
196
221
|
end
|
|
197
222
|
|
|
198
|
-
private_class_method :scan, :scan_dir, :ruby_files, :walk_dir, :within?, :extra_model_candidates, :extra_model_declarations, :class_declarations, :lookup, :qualified
|
|
223
|
+
private_class_method :scan, :scan_dir, :ruby_files, :walk_dir, :within?, :extra_model_candidates, :extra_model_declarations, :class_declarations, :lookup, :qualified, :concern_model?
|
|
199
224
|
|
|
200
225
|
# `kind: :models` reads model_paths: what model_details lists, not only app/models.
|
|
201
226
|
def each(root, kind:, skip_concerns: true, base_model: nil, &block)
|
|
@@ -207,7 +232,17 @@ module RailsAiContext
|
|
|
207
232
|
end
|
|
208
233
|
return paths(root, kind: kind, skip_concerns: skip_concerns, &read) unless kind == :models
|
|
209
234
|
|
|
210
|
-
|
|
235
|
+
model_names = nil
|
|
236
|
+
model_paths(root, base_model: base_model) do |record|
|
|
237
|
+
next read.call(record) unless skip_concerns && record.path_name.start_with?("Concerns::")
|
|
238
|
+
|
|
239
|
+
# The concerns root does not namespace its files, and one named for a class with a model superclass is a model.
|
|
240
|
+
source = SafeFile.read(record.path) or next
|
|
241
|
+
name = concerns_name(record.path_name, source)
|
|
242
|
+
# An inflection changes only the case of a path's name, so the names compare without it.
|
|
243
|
+
model_names ||= model_paths(root, base_model: base_model).to_set { |known| known.path_name.delete_prefix("Concerns::").downcase }.merge(MODEL_BASES.map(&:downcase))
|
|
244
|
+
block.call(record.with(source: source, path_name: name)) if concern_model?(source, name, model_names, base_model)
|
|
245
|
+
end
|
|
211
246
|
end
|
|
212
247
|
|
|
213
248
|
# The eager form: reads and parses every file for its declared name.
|
|
@@ -33,28 +33,26 @@ module RailsAiContext
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# One database's pending migrations, as `db:migrate:status:<name>` reads
|
|
36
|
-
# them: from that database's own schema_migrations, through
|
|
37
|
-
|
|
38
|
-
# are. The primary is asked through the app's own pool. The connection is
|
|
39
|
-
# made first, so a database that does not exist, or a server that does
|
|
40
|
-
# not answer, says so rather than reading as nothing pending; a SQLite
|
|
41
|
-
# file that is not there is said to be missing without connecting, which
|
|
42
|
-
# would create it.
|
|
43
|
-
#
|
|
44
|
-
# @param db_config [ActiveRecord::DatabaseConfigurations::DatabaseConfig]
|
|
45
|
-
# @return [Hash] { pending: [{ version:, name: }, ...] }, or { error: exception }
|
|
46
|
-
def self.of_database(db_config, migrate_dir, primary: false)
|
|
47
|
-
missing = DatabaseFile.missing(db_config)
|
|
48
|
-
return { error: missing } if missing
|
|
49
|
-
|
|
36
|
+
# them: from that database's own schema_migrations, through its pool.
|
|
37
|
+
def self.pending_on(pool, migrate_dir)
|
|
50
38
|
dirs = Array(migrate_dir).select { |dir| Dir.exist?(dir) }
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
39
|
+
return [] if dirs.empty?
|
|
40
|
+
# Rails 7.0's schema_migration is a model that finds its pool through the app's handler, where a leased pool is not.
|
|
41
|
+
return PendingMigrations.for(migrate_dir: dirs, applied: applied_versions(pool.connection)) if schema_migration_model?(pool)
|
|
42
|
+
|
|
43
|
+
pending_in(migration_context(dirs, pool), dirs)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.schema_migration_model?(pool)
|
|
47
|
+
!pool.respond_to?(:schema_migration) && !pool.connection.respond_to?(:internal_metadata)
|
|
48
|
+
end
|
|
49
|
+
private_class_method :schema_migration_model?
|
|
50
|
+
|
|
51
|
+
def self.applied_versions(connection)
|
|
52
|
+
table = ActiveRecord::Base.schema_migrations_table_name
|
|
53
|
+
connection.table_exists?(table) ? connection.select_values("SELECT version FROM #{connection.quote_table_name(table)}") : []
|
|
57
54
|
end
|
|
55
|
+
private_class_method :applied_versions
|
|
58
56
|
|
|
59
57
|
def self.pending_in(context, dirs)
|
|
60
58
|
applied = context.get_all_versions
|
|
@@ -65,32 +63,6 @@ module RailsAiContext
|
|
|
65
63
|
PendingMigrations.for(migrate_dir: dirs, applied: context.get_all_versions)
|
|
66
64
|
end
|
|
67
65
|
|
|
68
|
-
# What a pool established for another database belongs to: Rails names a
|
|
69
|
-
# pool by its owner, and an owner that is no primary class leaves the
|
|
70
|
-
# app's own pools alone (ActiveRecord::PendingMigrationConnection, which
|
|
71
|
-
# Rails 7.0 lacks and 7.1 shapes differently).
|
|
72
|
-
class TemporaryOwner
|
|
73
|
-
def self.primary_class?
|
|
74
|
-
false
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def self.current_preventing_writes
|
|
78
|
-
false
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def self.with_pool(db_config, primary:)
|
|
83
|
-
return yield(ActiveRecord::Base.connection_pool) if primary
|
|
84
|
-
|
|
85
|
-
handler = ActiveRecord::Base.connection_handler
|
|
86
|
-
begin
|
|
87
|
-
yield handler.establish_connection(db_config, owner_name: TemporaryOwner)
|
|
88
|
-
ensure
|
|
89
|
-
handler.remove_connection_pool(TemporaryOwner.name)
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
private_class_method :with_pool
|
|
93
|
-
|
|
94
66
|
# @return [ActiveRecord::MigrationContext]
|
|
95
67
|
def self.migration_context(migrate_dir, pool = ActiveRecord::Base.connection_pool)
|
|
96
68
|
# Rails 7.2+: MigrationContext takes a schema_migration + internal_metadata
|
|
@@ -118,6 +118,53 @@ module RailsAiContext
|
|
|
118
118
|
end
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
+
# Where a multi-database entry is configured when that is not the running environment, in words, or nil.
|
|
122
|
+
def scope_words(database)
|
|
123
|
+
return nil unless database.is_a?(Hash)
|
|
124
|
+
|
|
125
|
+
environments = Array(database[:environments])
|
|
126
|
+
return "#{environments.first} only" if environments.one?
|
|
127
|
+
return AppDatabases.environment_phrase(environments) if environments.any?
|
|
128
|
+
|
|
129
|
+
"committed dump only" if database[:dump_only]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# The same in parentheses, to follow the database's name.
|
|
133
|
+
def database_mark(database)
|
|
134
|
+
words = scope_words(database)
|
|
135
|
+
words ? " (#{words})" : ""
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# The multi_database section. A payload that holds none (a preset without it) gets the same list
|
|
139
|
+
# from the app databases, so no mark or name depends on the preset.
|
|
140
|
+
def multi_database(context)
|
|
141
|
+
section(context, :multi_database) || Introspectors::MultiDatabaseIntrospector.new(RailsAiContext.default_app).listing
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def databases(context)
|
|
145
|
+
Array(multi_database(context)[:databases]).grep(Hash)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def named_scope_words(context, name)
|
|
149
|
+
scope_words(databases(context).find { |database| database[:name].to_s == name.to_s })
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# What a generated file calls the primary: its own name where another database is the one named primary.
|
|
153
|
+
def primary_label(context)
|
|
154
|
+
name = multi_database(context)[:primary_name]
|
|
155
|
+
name && database_named?(context, "primary") ? name.to_s : "primary"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def database_named?(context, name)
|
|
159
|
+
databases(context).any? { |database| database[:name].to_s == name }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# The mark for a database known by its database.yml name.
|
|
163
|
+
def named_database_mark(context, name)
|
|
164
|
+
words = named_scope_words(context, name)
|
|
165
|
+
words ? " (#{words})" : ""
|
|
166
|
+
end
|
|
167
|
+
|
|
121
168
|
# Each database past the primary that the schema section read tables
|
|
122
169
|
# for, by its database.yml name.
|
|
123
170
|
def secondary_databases(schema)
|
|
@@ -193,7 +240,7 @@ module RailsAiContext
|
|
|
193
240
|
|
|
194
241
|
secondary = schema[:secondary_databases].is_a?(Hash) ? schema[:secondary_databases].keys : []
|
|
195
242
|
[ nil, *secondary ].filter_map do |db|
|
|
196
|
-
live = db.nil? && !parsed_schema?(schema) && schema[:adapter].to_s.
|
|
243
|
+
live = db.nil? && !parsed_schema?(schema) && DatabaseFamily.of(schema[:adapter].to_s).postgresql?
|
|
197
244
|
shown, data = Introspectors::SchemaIntrospector.qualified_table(name.to_s, database: db&.to_s, live: live)
|
|
198
245
|
# The fourth entry is the name the listing shows the table under, which its models read.
|
|
199
246
|
[ db&.to_s, name.to_s, data, shown ] if data
|
|
@@ -37,11 +37,10 @@ module RailsAiContext
|
|
|
37
37
|
migrate_dirs_of(root, Introspectors::SchemaDumpPath.database_name(root, dump_path))
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
# The same, for the
|
|
40
|
+
# The same, for the app database named `name`.
|
|
41
41
|
def migrate_dirs_of(root, name)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
[ File.join(root.to_s, "db", name == "primary" || entry ? "migrate" : "#{name}_migrate") ]
|
|
42
|
+
found = AppDatabases.for(root).of_dump_name(name)
|
|
43
|
+
found ? found.migrate_dirs : [ File.join(root.to_s, "db", name == "primary" ? "migrate" : "#{name}_migrate") ]
|
|
45
44
|
end
|
|
46
45
|
|
|
47
46
|
# Every versioned migration file under the directory or directories. One file scan behind
|
|
@@ -30,15 +30,6 @@ module RailsAiContext
|
|
|
30
30
|
POSTGRES_ONLY_TYPES = %w[jsonb hstore inet cidr citext tsvector ltree macaddr interval].freeze
|
|
31
31
|
MYSQL_ONLY_TYPES = %w[tinytext mediumtext longtext tinyblob mediumblob longblob].freeze
|
|
32
32
|
|
|
33
|
-
# structure.sql dialects as SchemaIntrospector records them, plus adapter names that
|
|
34
|
-
# mean one of them (activerecord-postgis-adapter registers `postgis`).
|
|
35
|
-
DIALECTS = {
|
|
36
|
-
"postgresql" => "PostgreSQL",
|
|
37
|
-
"postgis" => "PostgreSQL",
|
|
38
|
-
"mysql" => "MySQL",
|
|
39
|
-
"sqlite" => "SQLite"
|
|
40
|
-
}.freeze
|
|
41
|
-
|
|
42
33
|
DISPLAY_ORDER = %w[PostgreSQL MySQL SQLite].freeze
|
|
43
34
|
|
|
44
35
|
module_function
|
|
@@ -58,9 +49,13 @@ module RailsAiContext
|
|
|
58
49
|
from_examples(context) || from_gems(context) || "unknown"
|
|
59
50
|
end
|
|
60
51
|
|
|
61
|
-
# A connection's adapter_name as the other surfaces name it (PostGIS is PostgreSQL).
|
|
52
|
+
# A connection's adapter_name as the other surfaces name it (PostGIS is PostgreSQL, Trilogy is MySQL).
|
|
62
53
|
def display(adapter_name)
|
|
63
|
-
|
|
54
|
+
family = DatabaseFamily.of(adapter_name)
|
|
55
|
+
return adapter_name unless family.known?
|
|
56
|
+
|
|
57
|
+
# A label read back from the payload carries the mark, and the family reads past it.
|
|
58
|
+
adapter_name.to_s.end_with?(DatabaseFamily::DEFAULT_SUFFIX) ? family.name + DatabaseFamily::DEFAULT_SUFFIX : family.name
|
|
64
59
|
end
|
|
65
60
|
|
|
66
61
|
def label_with_reason(context)
|
|
@@ -91,9 +86,8 @@ module RailsAiContext
|
|
|
91
86
|
multi = context[:multi_database]
|
|
92
87
|
return nil unless Tools::SectionFetch.usable?(multi)
|
|
93
88
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
database_label(primary)
|
|
89
|
+
# The app databases list puts the primary first.
|
|
90
|
+
database_label(Array(multi[:databases]).first)
|
|
97
91
|
end
|
|
98
92
|
|
|
99
93
|
# A secondary database's adapter: its database.yml entry, else the dialect
|
|
@@ -114,15 +108,15 @@ module RailsAiContext
|
|
|
114
108
|
adapter = database[:adapter]
|
|
115
109
|
return nil if placeholder?(adapter)
|
|
116
110
|
|
|
117
|
-
name =
|
|
118
|
-
database[:adapter_default] ?
|
|
111
|
+
name = display(adapter.to_s)
|
|
112
|
+
database[:adapter_default] ? name + DatabaseFamily::DEFAULT_SUFFIX : name
|
|
119
113
|
end
|
|
120
114
|
|
|
121
115
|
def from_examples(context)
|
|
122
116
|
multi = context[:multi_database]
|
|
123
117
|
return nil unless Tools::SectionFetch.usable?(multi)
|
|
124
118
|
|
|
125
|
-
candidates(Array(multi[:example_adapters]).map { |name|
|
|
119
|
+
candidates(Array(multi[:example_adapters]).map { |name| display(name.to_s) })
|
|
126
120
|
end
|
|
127
121
|
|
|
128
122
|
def from_column_types(schema)
|
|
@@ -144,7 +138,8 @@ module RailsAiContext
|
|
|
144
138
|
def from_dialect(schema)
|
|
145
139
|
return nil unless schema.is_a?(Hash)
|
|
146
140
|
|
|
147
|
-
|
|
141
|
+
family = DatabaseFamily.of(schema[:dialect].to_s)
|
|
142
|
+
family.name if family.known?
|
|
148
143
|
end
|
|
149
144
|
|
|
150
145
|
# Never a guess: naming one of `pg` and `mysql2` can put a database the app does not
|