Class: Exwiw::DbSchemaGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/exwiw/db_schema_generator.rb

Overview

Generates (and tidies) a schema config from a live database connection instead of from an application's models — see DbIntrospector for why an application exwiw cannot load needs this.

It mirrors SchemaGenerator table for table and column for column, with one deliberate difference in how belongs_tos are reconciled (see #merged_belongs_tos) and one structural simplification: the output directory is flat, because a connection addresses exactly one database. A second database is a second run against a second connection, which is also how the extraction itself treats it.

Defined Under Namespace

Classes: TidyResult

Instance Method Summary collapse

Constructor Details

#initialize(introspector:, output_dir:, safe_new_columns: true) ⇒ DbSchemaGenerator

safe_new_columns matches SchemaGenerator's: on (the default) every column is emitted masked as far as its type allows and flagged needs_mask_decision: true, and #merge lets an already-decided entry win, so in practice only genuinely new columns keep that treatment.



44
45
46
47
48
# File 'lib/exwiw/db_schema_generator.rb', line 44

def initialize(introspector:, output_dir:, safe_new_columns: true)
  @introspector = introspector
  @output_dir = output_dir
  @safe_new_columns = safe_new_columns
end

Instance Method Details

#generate!Object

Write one config file per table in the database, merged with whatever is already on disk, and return the configs as written.

Unlike SchemaGenerator, the configs are built inside the write path rather than by a separate builder: what the generated belongs_tos are — and therefore which columns count as structural and must not be masked — depends on the existing file (see #merged_belongs_tos), so the two cannot be separated without introspecting the disk twice.



58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/exwiw/db_schema_generator.rb', line 58

def generate!
  FileUtils.mkdir_p(@output_dir)

  @introspector.table_names.map do |table_name|
    path = File.join(@output_dir, "#{table_name}.json")
    existing = read_config(path)
    generated = build_table(table_name, existing)

    config_to_write = existing ? existing.merge(generated) : generated
    File.write(path, JSON.pretty_generate(config_to_write.to_hash) + "\n")
    config_to_write
  end
end

#tidy!Object

Reconcile the config files on disk against the database, removing only what no longer exists there:

  • a config file whose table is gone is deleted,
  • columns a surviving table no longer has are dropped, and
  • a belongs_to pointing at a table that is gone is dropped.

The belongs_to case is this generator's own. Because #generate! only ever adds relations (never rewrites the list), a relation whose target table was dropped would otherwise survive every regeneration, and a belongs_to with no target table crashes dependency resolution at extraction time. An ignore: true entry is kept regardless: those are user tombstones recording a decision ("this relation is deliberately not extracted"), and deleting one would invite the next regeneration to add the edge back.

Like SchemaGenerator#tidy!, nothing is added or regenerated here: every surviving entry keeps its hand-edited comment / ignore / replace_with untouched. Returns a TidyResult describing the removals.



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/exwiw/db_schema_generator.rb', line 90

def tidy!
  result = TidyResult.new
  return result unless Dir.exist?(@output_dir)

  # Views are not generated (see DbIntrospector::Base#table_names), so a
  # config naming one is stale by the same definition as a dropped table.
  existing_tables = @introspector.table_names.to_set

  Dir[File.join(@output_dir, "*.json")].sort.each do |path|
    existing = TableConfig.from(JSON.parse(File.read(path)))

    unless existing_tables.include?(existing.name)
      File.delete(path)
      result.add_removed_table(existing.name)
      next
    end

    changed = false
    changed |= remove_stale_columns(existing, result)
    changed |= remove_dangling_belongs_tos(existing, existing_tables, result)
    File.write(path, JSON.pretty_generate(existing.to_hash) + "\n") if changed
  end

  result
end