exwiw 0.3.8 → 0.3.9
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 +6 -0
- data/README.md +17 -0
- data/lib/exwiw/schema_generator.rb +109 -7
- data/lib/exwiw/version.rb +1 -1
- data/lib/tasks/exwiw.rake +20 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea6a0b4fda446d66766027139136320937a59319bfe28d83ee595cf062a4023a
|
|
4
|
+
data.tar.gz: 1ce3d4a7601ae6b3f1fe6ca8ed22e977389ac7ab46973c33113206911893326e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '08ffcb1713110629652b4047b159613ab3d13e5fffcd092433282f28564328d1609572d8f159b1c0e7fddfab1be4fc92651b8f819ddfa44a913e18da04e4aeef'
|
|
7
|
+
data.tar.gz: 77529a1897ded0943898f60d8a55d54bfdf35f80fc512b57be7300adad9fc8c99767199de8e6d9fb9649dc2644fbcfc2252e445de2f5c38bc20aece5ae4a56d5
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.3.9] - 2026-06-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- New `exwiw:schema:tidy` rake task. It reconciles the existing schema config against the live database (read through the database connection, not the models) and removes only what no longer exists there: a config file whose table has been dropped is deleted, and columns recorded in a surviving table's config that the table no longer has are dropped from that file. Because it reads the database directly, a table that still exists in the database but has lost (or never had) a model is kept — only a genuinely-dropped table is removed. Unlike `schema:generate` it never adds or regenerates entries, so hand-edited `comment` / `ignore` / `replace_with` on surviving tables/columns are preserved. It honors `OUTPUT_DIR_PATH` and the per-database subdirectory layout, and prints the tables/columns it removed.
|
|
10
|
+
|
|
5
11
|
## [0.3.8] - 2026-06-02
|
|
6
12
|
|
|
7
13
|
### Added
|
data/README.md
CHANGED
|
@@ -144,6 +144,23 @@ By default, the schema files will be saved in the `exwiw` directory. You can spe
|
|
|
144
144
|
OUTPUT_DIR_PATH=custom_directory bundle exec rake exwiw:schema:generate
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
+
#### Tidying stale config (`schema:tidy`)
|
|
148
|
+
|
|
149
|
+
`schema:generate` adds and updates config files for the tables it finds, but it never deletes the config file of a table that has been dropped from the application. To reconcile the existing config against the current schema, run:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
bundle exec rake exwiw:schema:tidy
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`schema:tidy` compares the config files already on disk with the **live database** (read through the database connection, not the models) and removes only what no longer exists there:
|
|
156
|
+
|
|
157
|
+
- a config file whose table has been dropped from the database is **deleted**, and
|
|
158
|
+
- columns recorded in a surviving table's config that the table no longer has are **dropped** from that file.
|
|
159
|
+
|
|
160
|
+
Because it reads the database directly, a table that still exists in the database but has lost (or never had) an ActiveRecord model is **kept** — only a table that is genuinely gone is removed. (This is the deliberate counterpart to `generate`, which is model-driven and only ever adds what the models know about.)
|
|
161
|
+
|
|
162
|
+
It respects `OUTPUT_DIR_PATH` and the per-database subdirectory layout in the same way as `schema:generate`. Unlike `generate`, `tidy` never adds or regenerates entries — every surviving table/column (including hand-edited `comment` / `ignore` / `replace_with`) is left untouched, so it is safe to run on a customized config. The task prints which tables and columns it removed (or that the config was already tidy). Stale `belongs_tos` are not pruned by `tidy`; rerun `schema:generate` to refresh those.
|
|
163
|
+
|
|
147
164
|
#### Multiple databases
|
|
148
165
|
|
|
149
166
|
If the application uses Rails' multiple-database support (`connects_to`), `schema:generate` buckets models by the database they connect to and writes each database's config files into its own subdirectory of the output directory, named after the database config name (`primary`, `analytics`, ...):
|
|
@@ -5,6 +5,30 @@ require "json"
|
|
|
5
5
|
|
|
6
6
|
module Exwiw
|
|
7
7
|
class SchemaGenerator
|
|
8
|
+
# Summary of what `SchemaGenerator#tidy!` removed, returned so callers can
|
|
9
|
+
# report it. `removed_columns` maps a surviving table's name to the column
|
|
10
|
+
# names that were dropped from its config.
|
|
11
|
+
class TidyResult
|
|
12
|
+
attr_reader :removed_tables, :removed_columns
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@removed_tables = []
|
|
16
|
+
@removed_columns = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_removed_table(table_name)
|
|
20
|
+
@removed_tables << table_name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_removed_column(table_name, column_name)
|
|
24
|
+
(@removed_columns[table_name] ||= []) << column_name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def empty?
|
|
28
|
+
@removed_tables.empty? && @removed_columns.empty?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
8
32
|
# ActiveStorage tracks generated image variants in this table. Its rows are
|
|
9
33
|
# derivative and regenerable — ActiveStorage lazily (re)creates a variant the
|
|
10
34
|
# next time it is requested — so there is little value in exporting them. More
|
|
@@ -35,6 +59,60 @@ module Exwiw
|
|
|
35
59
|
groups
|
|
36
60
|
end
|
|
37
61
|
|
|
62
|
+
# Reconcile the config files already on disk against the live database,
|
|
63
|
+
# removing only what no longer exists there:
|
|
64
|
+
#
|
|
65
|
+
# - a config file whose table is no longer present is deleted, and
|
|
66
|
+
# - columns recorded in a surviving table's config that the table no
|
|
67
|
+
# longer has are dropped from that file.
|
|
68
|
+
#
|
|
69
|
+
# The source of truth is the database connection (`data_sources` for table
|
|
70
|
+
# existence — which covers views too — and `columns` for the column list),
|
|
71
|
+
# NOT `build_table_groups`. `build_table_groups` only knows about tables
|
|
72
|
+
# that still have an ActiveRecord model, so reconciling against it would
|
|
73
|
+
# delete the config of a table that is still present in the database but
|
|
74
|
+
# has merely lost (or never had) a model. Reading the connection directly
|
|
75
|
+
# avoids that: only a table that is genuinely gone from the database is
|
|
76
|
+
# removed.
|
|
77
|
+
#
|
|
78
|
+
# Unlike `generate!`, tidy never adds or regenerates entries: every
|
|
79
|
+
# surviving table/column — including its hand-edited `comment` / `ignore` /
|
|
80
|
+
# `replace_with` — is left untouched, and only the stale entries are
|
|
81
|
+
# stripped. (Removing a deleted column is something `generate!` already does
|
|
82
|
+
# incidentally via #merge, but `generate!` can never delete the config file
|
|
83
|
+
# of a removed table, which is the gap this fills.) Returns a TidyResult
|
|
84
|
+
# describing the removals so callers (e.g. the rake task) can report them.
|
|
85
|
+
def tidy!
|
|
86
|
+
result = TidyResult.new
|
|
87
|
+
|
|
88
|
+
model_db_groups.each do |db_name, _group_models, conn|
|
|
89
|
+
dir = config_dir_for(db_name)
|
|
90
|
+
next unless Dir.exist?(dir)
|
|
91
|
+
|
|
92
|
+
existing_data_sources = conn.data_sources.to_set
|
|
93
|
+
|
|
94
|
+
Dir[File.join(dir, "*.json")].sort.each do |path|
|
|
95
|
+
existing = TableConfig.from(JSON.parse(File.read(path)))
|
|
96
|
+
|
|
97
|
+
unless existing_data_sources.include?(existing.name)
|
|
98
|
+
File.delete(path)
|
|
99
|
+
result.add_removed_table(existing.name)
|
|
100
|
+
next
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
valid_column_names = conn.columns(existing.name).map(&:name).to_set
|
|
104
|
+
stale_columns = existing.columns.reject { |column| valid_column_names.include?(column.name) }
|
|
105
|
+
next if stale_columns.empty?
|
|
106
|
+
|
|
107
|
+
existing.columns = existing.columns.select { |column| valid_column_names.include?(column.name) }
|
|
108
|
+
File.write(path, JSON.pretty_generate(existing.to_hash) + "\n")
|
|
109
|
+
stale_columns.each { |column| result.add_removed_column(existing.name, column.name) }
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
result
|
|
114
|
+
end
|
|
115
|
+
|
|
38
116
|
# Returns a Hash keyed by the database name.
|
|
39
117
|
#
|
|
40
118
|
# - Single-database setup: the only key is `nil`, signalling that the table
|
|
@@ -44,18 +122,35 @@ module Exwiw
|
|
|
44
122
|
# mapping to that database's table configs. They are written into
|
|
45
123
|
# `output_dir/<db_name>/`.
|
|
46
124
|
def build_table_groups
|
|
125
|
+
model_db_groups.each_with_object({}) do |(db_name, group_models, conn), result|
|
|
126
|
+
result[db_name] = build_tables_for(group_models, conn)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# The per-database grouping that both `build_table_groups` and `tidy!` work
|
|
131
|
+
# from: `[[db_name, models, connection], ...]`.
|
|
132
|
+
#
|
|
133
|
+
# - Single-database setup: one entry keyed by `nil` (configs written flat
|
|
134
|
+
# into `output_dir`). When there are no models at all, fall back to the
|
|
135
|
+
# default connection so callers still have a connection to inspect.
|
|
136
|
+
# - Multi-database setup (Rails `connects_to`): one entry per database,
|
|
137
|
+
# keyed by `connection_db_config.name` ("primary" / "analytics", ...).
|
|
138
|
+
#
|
|
139
|
+
# The db_name <-> connection mapping is necessarily model-derived: which
|
|
140
|
+
# databases the app talks to is only declared on the model side (via
|
|
141
|
+
# `connects_to`). What each consumer reads *through* that connection differs
|
|
142
|
+
# — `build_table_groups` builds configs from the models, while `tidy!`
|
|
143
|
+
# reads the live database's actual tables/columns.
|
|
144
|
+
private def model_db_groups
|
|
47
145
|
models = concrete_models
|
|
48
146
|
grouped = models.group_by { |model| database_name_for(model) }
|
|
49
147
|
|
|
50
148
|
if grouped.size <= 1
|
|
51
149
|
conn = models.empty? ? ActiveRecord::Base.connection : models.first.connection
|
|
52
|
-
return
|
|
150
|
+
return [[nil, models, conn]]
|
|
53
151
|
end
|
|
54
152
|
|
|
55
|
-
grouped.
|
|
56
|
-
conn = group_models.first.connection
|
|
57
|
-
result[db_name] = build_tables_for(group_models, conn)
|
|
58
|
-
end
|
|
153
|
+
grouped.map { |db_name, group_models| [db_name, group_models, group_models.first.connection] }
|
|
59
154
|
end
|
|
60
155
|
|
|
61
156
|
# Backwards-compatible flat list of all table configs. Only meaningful for
|
|
@@ -67,11 +162,18 @@ module Exwiw
|
|
|
67
162
|
|
|
68
163
|
def write_groups(groups)
|
|
69
164
|
groups.each do |db_name, tables|
|
|
70
|
-
|
|
71
|
-
write_files(dir, tables)
|
|
165
|
+
write_files(config_dir_for(db_name), tables)
|
|
72
166
|
end
|
|
73
167
|
end
|
|
74
168
|
|
|
169
|
+
# The directory a database group's config files live in. A single-database
|
|
170
|
+
# setup (`db_name` is nil) writes flat into `output_dir`; a multi-database
|
|
171
|
+
# setup writes into `output_dir/<db_name>/`. Shared by `write_groups` and
|
|
172
|
+
# `tidy!` so the two operations agree on file locations.
|
|
173
|
+
private def config_dir_for(db_name)
|
|
174
|
+
db_name.nil? ? @output_dir : File.join(@output_dir, db_name)
|
|
175
|
+
end
|
|
176
|
+
|
|
75
177
|
def write_files(dir, tables)
|
|
76
178
|
FileUtils.mkdir_p(dir)
|
|
77
179
|
|
data/lib/exwiw/version.rb
CHANGED
data/lib/tasks/exwiw.rake
CHANGED
|
@@ -11,6 +11,26 @@ namespace :exwiw do
|
|
|
11
11
|
).generate!
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
desc "Remove tables/columns from the schema config that no longer exist in the application"
|
|
15
|
+
task tidy: :environment do
|
|
16
|
+
require "exwiw"
|
|
17
|
+
|
|
18
|
+
result = Exwiw::SchemaGenerator.from_rails_application(
|
|
19
|
+
output_dir: ENV["OUTPUT_DIR_PATH"] || "exwiw",
|
|
20
|
+
).tidy!
|
|
21
|
+
|
|
22
|
+
if result.empty?
|
|
23
|
+
puts "exwiw: schema config is already tidy; nothing to remove."
|
|
24
|
+
else
|
|
25
|
+
result.removed_tables.each do |name|
|
|
26
|
+
puts "exwiw: removed config for table '#{name}' (no longer exists in the application)."
|
|
27
|
+
end
|
|
28
|
+
result.removed_columns.each do |table_name, columns|
|
|
29
|
+
puts "exwiw: removed column(s) #{columns.join(', ')} from '#{table_name}' (no longer in the table)."
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
14
34
|
desc "Generate schema from a Mongoid application"
|
|
15
35
|
task generate_mongoid: :environment do
|
|
16
36
|
require "exwiw"
|