Class: Exwiw::SchemaGenerator
- Inherits:
-
Object
- Object
- Exwiw::SchemaGenerator
- Defined in:
- lib/exwiw/schema_generator.rb
Defined Under Namespace
Classes: TidyResult
Constant Summary collapse
- ACTIVE_STORAGE_VARIANT_RECORDS_TABLE =
ActiveStorage tracks generated image variants in this table. Its rows are derivative and regenerable — ActiveStorage lazily (re)creates a variant the next time it is requested — so there is little value in exporting them. More importantly, the table has no belongs_to path to any dump target, which would land it in QueryAstBuilder’s “no relation -> dump all” branch, while its ‘blob_id` references active_storage_blobs, which the reverse “referenced_by” extraction narrows to only the attachment-referenced blobs. A full variant_records dump can therefore reference blobs that were never exported (a foreign-key violation on import). So the table is emitted with `ignore: true` (data extraction skipped) and excluded as a polymorphic `record` target so the non-ignored attachments table carries no dangling belongs_to to it.
"active_storage_variant_records"- CROSS_DATABASE_IGNORE_TYPE =
"cross_database"
Class Method Summary collapse
-
.cross_database_belongs_tos(groups) ⇒ Object
Flatten the generated ‘groups` (the Hash returned by generate! / build_table_groups) into the list of cross-database belongs_tos the generator auto-ignored, so a caller (the rake task) can surface them.
- .from_rails_application(output_dir:) ⇒ Object
Instance Method Summary collapse
-
#build_table_groups ⇒ Object
Returns a Hash keyed by the database name.
-
#build_tables ⇒ Object
Backwards-compatible flat list of all table configs.
- #generate! ⇒ Object
-
#initialize(models:, output_dir:) ⇒ SchemaGenerator
constructor
A new instance of SchemaGenerator.
-
#tidy! ⇒ Object
Reconcile the config files already on disk against the live database, removing only what no longer exists there:.
- #write_files(dir, tables) ⇒ Object
- #write_groups(groups) ⇒ Object
Constructor Details
#initialize(models:, output_dir:) ⇒ SchemaGenerator
Returns a new instance of SchemaGenerator.
51 52 53 54 |
# File 'lib/exwiw/schema_generator.rb', line 51 def initialize(models:, output_dir:) @models = models @output_dir = output_dir end |
Class Method Details
.cross_database_belongs_tos(groups) ⇒ Object
Flatten the generated ‘groups` (the Hash returned by generate! / build_table_groups) into the list of cross-database belongs_tos the generator auto-ignored, so a caller (the rake task) can surface them. Each entry is `{ table:, foreign_key:, target: }`. Empty for single-database apps.
66 67 68 69 70 71 72 73 74 |
# File 'lib/exwiw/schema_generator.rb', line 66 def self.cross_database_belongs_tos(groups) groups.values.flatten.flat_map do |table| next [] unless table.respond_to?(:belongs_tos) table.belongs_tos .select { |bt| bt.ignore_type == CROSS_DATABASE_IGNORE_TYPE } .map { |bt| { table: table.name, foreign_key: bt.foreign_key, target: bt.table_name } } end end |
.from_rails_application(output_dir:) ⇒ Object
46 47 48 49 |
# File 'lib/exwiw/schema_generator.rb', line 46 def self.from_rails_application(output_dir:) Rails.application.eager_load! new(models: ActiveRecord::Base.descendants, output_dir: output_dir) end |
Instance Method Details
#build_table_groups ⇒ Object
Returns a Hash keyed by the database name.
-
Single-database setup: the only key is ‘nil`, signalling that the table configs should be written flat into `output_dir` (backwards compatible).
-
Multi-database setup (Rails ‘connects_to`): one key per database (`connection_db_config.name`, e.g. “primary” / “analytics”), each mapping to that database’s table configs. They are written into ‘output_dir/<db_name>/`.
138 139 140 141 142 |
# File 'lib/exwiw/schema_generator.rb', line 138 def build_table_groups model_db_groups.each_with_object({}) do |(db_name, group_models, conn), result| result[db_name] = build_tables_for(group_models, conn) end end |
#build_tables ⇒ Object
Backwards-compatible flat list of all table configs. Only meaningful for a single-database setup; for multi-database setups prefer ‘#build_table_groups` so the database association is preserved.
173 174 175 |
# File 'lib/exwiw/schema_generator.rb', line 173 def build_tables build_table_groups.values.flatten end |
#generate! ⇒ Object
56 57 58 59 60 |
# File 'lib/exwiw/schema_generator.rb', line 56 def generate! groups = build_table_groups write_groups(groups) groups end |
#tidy! ⇒ Object
Reconcile the config files already on disk against the live database, removing only what no longer exists there:
-
a config file whose table is no longer present is deleted, and
-
columns recorded in a surviving table’s config that the table no longer has are dropped from that file.
The source of truth is the database connection (‘data_sources` for table existence — which covers views too — and `columns` for the column list), NOT `build_table_groups`. `build_table_groups` only knows about tables that still have an ActiveRecord model, so reconciling against it would delete the config of a table that is still present in the database but has merely lost (or never had) a model. Reading the connection directly avoids that: only a table that is genuinely gone from the database is removed.
Unlike ‘generate!`, tidy never adds or regenerates entries: every surviving table/column — including its hand-edited `comment` / `ignore` / `replace_with` — is left untouched, and only the stale entries are stripped. (Removing a deleted column is something `generate!` already does incidentally via #merge, but `generate!` can never delete the config file of a removed table, which is the gap this fills.) Returns a TidyResult describing the removals so callers (e.g. the rake task) can report them.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/exwiw/schema_generator.rb', line 99 def tidy! result = TidyResult.new model_db_groups.each do |db_name, _group_models, conn| dir = config_dir_for(db_name) next unless Dir.exist?(dir) existing_data_sources = conn.data_sources.to_set Dir[File.join(dir, "*.json")].sort.each do |path| existing = TableConfig.from(JSON.parse(File.read(path))) unless existing_data_sources.include?(existing.name) File.delete(path) result.add_removed_table(existing.name) next end valid_column_names = conn.columns(existing.name).map(&:name).to_set stale_columns = existing.columns.reject { |column| valid_column_names.include?(column.name) } next if stale_columns.empty? existing.columns = existing.columns.select { |column| valid_column_names.include?(column.name) } File.write(path, JSON.pretty_generate(existing.to_hash) + "\n") stale_columns.each { |column| result.add_removed_column(existing.name, column.name) } end end result end |
#write_files(dir, tables) ⇒ Object
191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/exwiw/schema_generator.rb', line 191 def write_files(dir, tables) FileUtils.mkdir_p(dir) tables.each do |table| path = File.join(dir, "#{table.name}.json") config_to_write = if File.exist?(path) TableConfig.from(JSON.parse(File.read(path))).merge(table) else table end File.write(path, JSON.pretty_generate(config_to_write.to_hash) + "\n") end end |
#write_groups(groups) ⇒ Object
177 178 179 180 181 |
# File 'lib/exwiw/schema_generator.rb', line 177 def write_groups(groups) groups.each do |db_name, tables| write_files(config_dir_for(db_name), tables) end end |