exwiw 0.2.6 → 0.2.8
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 +16 -0
- data/README.md +63 -1
- data/lib/exwiw/adapter/mysql2_adapter.rb +19 -1
- data/lib/exwiw/adapter/postgresql_adapter.rb +19 -1
- data/lib/exwiw/adapter/sqlite3_adapter.rb +19 -1
- data/lib/exwiw/belongs_to.rb +13 -0
- data/lib/exwiw/query_ast.rb +11 -2
- data/lib/exwiw/query_ast_builder.rb +39 -1
- data/lib/exwiw/schema_generator.rb +63 -15
- data/lib/exwiw/table_config.rb +8 -1
- data/lib/exwiw/version.rb +1 -1
- 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: 21a9432692220a23b0109497b0ffcb3f1c7f0fc360248c3590f730a4a98452ec
|
|
4
|
+
data.tar.gz: bf0847f90a24c8da3cf078f1c18e5d22f9827b705bc13945ddfc28f8e42460f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c59e201f1180e2fb6191506647b78a82c43d7d124117c0d87e2cdcbfd8fa68ff30653accc9a22d82434d7e680496eed41d91c7b150b8b884b3b545b344b91d72
|
|
7
|
+
data.tar.gz: 600e4a3b8d134d43182b81017b8b4ad9a352824a76a351300ca3fc22ee4942ceccf6f8404eb6e04e2a2320a79849ddc6b3cacf0c3510c7b8acca0c06820eaa22
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.8] - 2026-05-31
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `schema:generate` now supports polymorphic `belongs_to` associations. Each polymorphic relation (`belongs_to :reviewable, polymorphic: true`) is expanded into one `belongs_to` entry per concrete target table — discovered from the other models' `has_many` / `has_one ..., as:` declarations — carrying `foreign_type` (the type column, e.g. `reviewable_type`) and `type_value` (the stored type, e.g. `"Product"`). Targets are ordered by table name so the generated output is stable across Ruby versions. At dump time, a polymorphic `belongs_to` on the path to the dump target is constrained by both the foreign key and the type column — in both the `SELECT` and the `delete-*.sql` bulk-delete subquery — so only rows of the matching type are extracted. ([#43](https://github.com/heyinc/exwiw/pull/43))
|
|
10
|
+
|
|
11
|
+
## [0.2.7] - 2026-05-30
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `schema:generate` no longer crashes on models with a composite primary key (`primary_key` returns an Array). Such tables are emitted with `skip: true`, tagged `type: "unsupported_composite_primary_key"`, and annotated with a `comment` listing the key columns. `columns` / `belongs_tos` are retained so the entry can be wired up once composite-key support lands; for now `skip: true` excludes it from extraction. `skip: true` tables no longer require `primary_key` at load time.
|
|
16
|
+
|
|
17
|
+
### Notes
|
|
18
|
+
|
|
19
|
+
- `schema:generate` multi-database support: each database keeps its own Rails migration history, so a per-database `schema_migrations` / `ar_internal_metadata` entry is emitted for every database that has one. Known limitation: the rails-managed table *name* is resolved from the global `ActiveRecord::Base.schema_migrations_table_name` / `internal_metadata_table_name` accessors, so a per-database override of those names is not detected and the table will be missing from that database's generated configs.
|
|
20
|
+
|
|
5
21
|
## [0.2.6] - 2026-05-29
|
|
6
22
|
|
|
7
23
|
### Added
|
data/README.md
CHANGED
|
@@ -142,7 +142,11 @@ exwiw/
|
|
|
142
142
|
analytics_events.json
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
Rails
|
|
145
|
+
Each database keeps its own Rails migration history, so a `schema_migrations` (and `ar_internal_metadata`) entry is emitted under every database that contains one — the example above shows `primary/schema_migrations.json` and would also produce `analytics/schema_migrations.json` when the analytics database has its own migration table. Single-database applications are unaffected and continue to write files flat into the output directory.
|
|
146
|
+
|
|
147
|
+
**Limitations**
|
|
148
|
+
|
|
149
|
+
- The rails-managed table *names* are resolved from the global `ActiveRecord::Base.schema_migrations_table_name` / `internal_metadata_table_name` accessors, which are shared across all connections. A per-database override of these names is not detected, so such a table will be missing from that database's generated configs.
|
|
146
150
|
|
|
147
151
|
### Configuration
|
|
148
152
|
|
|
@@ -237,6 +241,47 @@ Constraints:
|
|
|
237
241
|
- Specifying a skipped table as `--target-table` raises `ArgumentError`.
|
|
238
242
|
- `skip: true` is preserved by `exwiw:schema:generate` regenerations (the receiver value wins over the auto-generated config).
|
|
239
243
|
|
|
244
|
+
### Polymorphic `belongs_to`
|
|
245
|
+
|
|
246
|
+
A Rails polymorphic association (`belongs_to :reviewable, polymorphic: true`) does not point at a single table — the target row is selected at runtime by a type column. exwiw models this as **one `belongs_to` entry per concrete target table**, each carrying two extra fields:
|
|
247
|
+
|
|
248
|
+
- `foreign_type` — the type column on *this* table (e.g. `reviewable_type`).
|
|
249
|
+
- `type_value` — the value stored in that column for this target (e.g. `"Product"`), i.e. the target model's `polymorphic_name`.
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"name": "reviews",
|
|
254
|
+
"primary_key": "id",
|
|
255
|
+
"belongs_tos": [
|
|
256
|
+
{
|
|
257
|
+
"table_name": "products",
|
|
258
|
+
"foreign_key": "reviewable_id",
|
|
259
|
+
"foreign_type": "reviewable_type",
|
|
260
|
+
"type_value": "Product"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"table_name": "shops",
|
|
264
|
+
"foreign_key": "reviewable_id",
|
|
265
|
+
"foreign_type": "reviewable_type",
|
|
266
|
+
"type_value": "Shop"
|
|
267
|
+
}
|
|
268
|
+
],
|
|
269
|
+
"columns": [{ "name": "id" }, { "name": "reviewable_type" }, { "name": "reviewable_id" }]
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`exwiw:schema:generate` expands a polymorphic `belongs_to` automatically: it finds every model that registers the association as a target via `has_many` / `has_one ..., as: :reviewable` and emits one entry per target table (ordered by table name so the output is stable across Ruby versions). A plain (non-polymorphic) `belongs_to` simply omits `foreign_type` / `type_value`.
|
|
274
|
+
|
|
275
|
+
At dump time, when a polymorphic `belongs_to` lies on the path to the dump target, exwiw constrains **both** the foreign key and the type column, so only rows of the matching type are extracted. For example, dumping `products` pulls only reviews whose `reviewable_type = 'Product'`:
|
|
276
|
+
|
|
277
|
+
```sql
|
|
278
|
+
SELECT reviews.* FROM reviews
|
|
279
|
+
WHERE reviews.reviewable_id IN (/* products subquery */)
|
|
280
|
+
AND reviews.reviewable_type = 'Product'
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The same type filter is applied on the join path — and in the matching `delete-*.sql` bulk-delete subquery — when the polymorphic table is an intermediate hop rather than the directly-dumped table.
|
|
284
|
+
|
|
240
285
|
### Rails-managed tables (special `type` values)
|
|
241
286
|
|
|
242
287
|
Some tables are owned by Rails itself rather than the application — they have no ActiveRecord model and Rails reserves the right to evolve their column shape between versions (e.g. `schema_migrations`, `ar_internal_metadata`). exwiw treats them as a distinct category via the `type` field on a table config:
|
|
@@ -268,6 +313,23 @@ Constraints:
|
|
|
268
313
|
- A rails-managed table cannot be used as `--target-table`.
|
|
269
314
|
- In multi-database setups, the rails-managed entry is emitted under whichever database's connection actually contains the table (see [Multiple databases](#multiple-databases)). The table name itself is still derived from the global `ActiveRecord::Base.schema_migrations_table_name` / `internal_metadata_table_name` (prefix/suffix) accessors.
|
|
270
315
|
|
|
316
|
+
### Composite primary keys (unsupported)
|
|
317
|
+
|
|
318
|
+
exwiw does not yet support tables with a composite primary key. When `exwiw:schema:generate` encounters a model whose `primary_key` is an array, it still emits a config entry so the table is not silently dropped, but marks it `skip: true`, tags it `type: "unsupported_composite_primary_key"`, and records the key columns in a `comment`:
|
|
319
|
+
|
|
320
|
+
```json
|
|
321
|
+
{
|
|
322
|
+
"name": "composite_pk_records",
|
|
323
|
+
"type": "unsupported_composite_primary_key",
|
|
324
|
+
"skip": true,
|
|
325
|
+
"comment": "exwiw does not support composite primary keys (organization_id, location_id); data extraction is skipped.",
|
|
326
|
+
"belongs_tos": [],
|
|
327
|
+
"columns": [{ "name": "organization_id" }, { "name": "location_id" }, { "name": "name" }]
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Unlike rails-managed entries, `columns` and `belongs_tos` are retained so the entry is ready to wire up once composite-key support lands. The `type` is purely a marker — `skip: true` is what actually excludes the table from extraction, so removing `skip` (and supplying a workable `primary_key`) lets you opt the table back in manually.
|
|
332
|
+
|
|
271
333
|
### Bulk insert chunk size
|
|
272
334
|
|
|
273
335
|
`bulk_insert_chunk_size` splits the generated `INSERT` statement into multiple statements, each containing at most the specified number of rows. This is useful when the number of records per table is large enough to hit limits like MySQL's `max_allowed_packet`.
|
|
@@ -136,7 +136,18 @@ module Exwiw
|
|
|
136
136
|
|
|
137
137
|
foreign_key = first_join.foreign_key
|
|
138
138
|
subquery_sql = compile_ast(subquery_ast)
|
|
139
|
-
sql += "\nWHERE #{select_query_ast.from_table_name}.#{foreign_key} IN (#{subquery_sql})
|
|
139
|
+
sql += "\nWHERE #{select_query_ast.from_table_name}.#{foreign_key} IN (#{subquery_sql})"
|
|
140
|
+
|
|
141
|
+
# first_join.base_where_clauses は外側の削除対象テーブル
|
|
142
|
+
# (from_table_name) 上の条件 (polymorphic 型カラム等)。subquery には
|
|
143
|
+
# 含まれないため、外側の WHERE に追加する。これにより、別の
|
|
144
|
+
# polymorphic 型に属する行まで削除してしまうのを防ぐ。
|
|
145
|
+
first_join.base_where_clauses.each do |where|
|
|
146
|
+
next unless where.is_a?(Exwiw::QueryAst::WhereClause)
|
|
147
|
+
|
|
148
|
+
sql += " AND #{compile_where_condition(where, select_query_ast.from_table_name)}"
|
|
149
|
+
end
|
|
150
|
+
sql += ";"
|
|
140
151
|
|
|
141
152
|
sql
|
|
142
153
|
end
|
|
@@ -159,6 +170,13 @@ module Exwiw
|
|
|
159
170
|
compiled_where_condition = compile_where_condition(where, join.join_table_name)
|
|
160
171
|
sql += " AND #{compiled_where_condition}"
|
|
161
172
|
end
|
|
173
|
+
|
|
174
|
+
# base_where_clauses は結合元テーブル (base_table_name) に対して
|
|
175
|
+
# コンパイルする。polymorphic な結合元テーブルの型カラム絞り込み等。
|
|
176
|
+
join.base_where_clauses.each do |where|
|
|
177
|
+
compiled_where_condition = compile_where_condition(where, join.base_table_name)
|
|
178
|
+
sql += " AND #{compiled_where_condition}"
|
|
179
|
+
end
|
|
162
180
|
end
|
|
163
181
|
|
|
164
182
|
if query_ast.where_clauses.any?
|
|
@@ -178,7 +178,18 @@ module Exwiw
|
|
|
178
178
|
|
|
179
179
|
foreign_key = first_join.foreign_key
|
|
180
180
|
subquery_sql = compile_ast(subquery_ast)
|
|
181
|
-
sql += "\nWHERE #{select_query_ast.from_table_name}.#{foreign_key} IN (#{subquery_sql})
|
|
181
|
+
sql += "\nWHERE #{select_query_ast.from_table_name}.#{foreign_key} IN (#{subquery_sql})"
|
|
182
|
+
|
|
183
|
+
# first_join.base_where_clauses は外側の削除対象テーブル
|
|
184
|
+
# (from_table_name) 上の条件 (polymorphic 型カラム等)。subquery には
|
|
185
|
+
# 含まれないため、外側の WHERE に追加する。これにより、別の
|
|
186
|
+
# polymorphic 型に属する行まで削除してしまうのを防ぐ。
|
|
187
|
+
first_join.base_where_clauses.each do |where|
|
|
188
|
+
next unless where.is_a?(Exwiw::QueryAst::WhereClause)
|
|
189
|
+
|
|
190
|
+
sql += " AND #{compile_where_condition(where, select_query_ast.from_table_name)}"
|
|
191
|
+
end
|
|
192
|
+
sql += ";"
|
|
182
193
|
|
|
183
194
|
sql
|
|
184
195
|
end
|
|
@@ -201,6 +212,13 @@ module Exwiw
|
|
|
201
212
|
compiled_where_condition = compile_where_condition(where, join.join_table_name)
|
|
202
213
|
sql += " AND #{compiled_where_condition}"
|
|
203
214
|
end
|
|
215
|
+
|
|
216
|
+
# base_where_clauses は結合元テーブル (base_table_name) に対して
|
|
217
|
+
# コンパイルする。polymorphic な結合元テーブルの型カラム絞り込み等。
|
|
218
|
+
join.base_where_clauses.each do |where|
|
|
219
|
+
compiled_where_condition = compile_where_condition(where, join.base_table_name)
|
|
220
|
+
sql += " AND #{compiled_where_condition}"
|
|
221
|
+
end
|
|
204
222
|
end
|
|
205
223
|
|
|
206
224
|
if query_ast.where_clauses.any?
|
|
@@ -123,7 +123,18 @@ module Exwiw
|
|
|
123
123
|
|
|
124
124
|
foreign_key = first_join.foreign_key
|
|
125
125
|
subquery_sql = compile_ast(subquery_ast)
|
|
126
|
-
sql += "\nWHERE #{select_query_ast.from_table_name}.#{foreign_key} IN (#{subquery_sql})
|
|
126
|
+
sql += "\nWHERE #{select_query_ast.from_table_name}.#{foreign_key} IN (#{subquery_sql})"
|
|
127
|
+
|
|
128
|
+
# first_join.base_where_clauses は外側の削除対象テーブル
|
|
129
|
+
# (from_table_name) 上の条件 (polymorphic 型カラム等)。subquery には
|
|
130
|
+
# 含まれないため、外側の WHERE に追加する。これにより、別の
|
|
131
|
+
# polymorphic 型に属する行まで削除してしまうのを防ぐ。
|
|
132
|
+
first_join.base_where_clauses.each do |where|
|
|
133
|
+
next unless where.is_a?(Exwiw::QueryAst::WhereClause)
|
|
134
|
+
|
|
135
|
+
sql += " AND #{compile_where_condition(where, select_query_ast.from_table_name)}"
|
|
136
|
+
end
|
|
137
|
+
sql += ";"
|
|
127
138
|
|
|
128
139
|
sql
|
|
129
140
|
end
|
|
@@ -146,6 +157,13 @@ module Exwiw
|
|
|
146
157
|
compiled_where_condition = compile_where_condition(where, join.join_table_name)
|
|
147
158
|
sql += " AND #{compiled_where_condition}"
|
|
148
159
|
end
|
|
160
|
+
|
|
161
|
+
# base_where_clauses は結合元テーブル (base_table_name) に対して
|
|
162
|
+
# コンパイルする。polymorphic な結合元テーブルの型カラム絞り込み等。
|
|
163
|
+
join.base_where_clauses.each do |where|
|
|
164
|
+
compiled_where_condition = compile_where_condition(where, join.base_table_name)
|
|
165
|
+
sql += " AND #{compiled_where_condition}"
|
|
166
|
+
end
|
|
149
167
|
end
|
|
150
168
|
|
|
151
169
|
if query_ast.where_clauses.any?
|
data/lib/exwiw/belongs_to.rb
CHANGED
|
@@ -6,9 +6,22 @@ module Exwiw
|
|
|
6
6
|
|
|
7
7
|
attribute :foreign_key, String
|
|
8
8
|
attribute :table_name, String
|
|
9
|
+
# polymorphic 関連の場合のみ設定される。`foreign_type` は型を格納するカラム名
|
|
10
|
+
# (例: `reviewable_type`)、`type_value` はそのカラムに入る値 (例: `"Product"`)。
|
|
11
|
+
# 非 polymorphic の belongs_to では両方とも nil。
|
|
12
|
+
attribute :foreign_type, optional(String), skip_serializing_if_nil: true
|
|
13
|
+
attribute :type_value, optional(String), skip_serializing_if_nil: true
|
|
9
14
|
|
|
10
15
|
def self.from_symbol_keys(hash)
|
|
11
16
|
from(hash.transform_keys(&:to_s))
|
|
12
17
|
end
|
|
18
|
+
|
|
19
|
+
def polymorphic?
|
|
20
|
+
!foreign_type.nil?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_hash
|
|
24
|
+
super.compact
|
|
25
|
+
end
|
|
13
26
|
end
|
|
14
27
|
end
|
data/lib/exwiw/query_ast.rb
CHANGED
|
@@ -3,14 +3,20 @@
|
|
|
3
3
|
module Exwiw
|
|
4
4
|
module QueryAst
|
|
5
5
|
class JoinClause
|
|
6
|
-
|
|
6
|
+
# `where_clauses` はこの join の join_table_name (= 結合先テーブル) に対して
|
|
7
|
+
# コンパイルされる。一方 `base_where_clauses` は base_table_name (= 結合元
|
|
8
|
+
# テーブル) に対してコンパイルされる。後者は、結合元テーブルが結合先へ
|
|
9
|
+
# polymorphic belongs_to していて型カラム (foreign_type) が結合元テーブル
|
|
10
|
+
# 側に存在するケースのために使う。
|
|
11
|
+
attr_reader :base_table_name, :foreign_key, :join_table_name, :primary_key, :where_clauses, :base_where_clauses
|
|
7
12
|
|
|
8
|
-
def initialize(base_table_name:, foreign_key:, join_table_name:, primary_key:, where_clauses: [])
|
|
13
|
+
def initialize(base_table_name:, foreign_key:, join_table_name:, primary_key:, where_clauses: [], base_where_clauses: [])
|
|
9
14
|
@base_table_name = base_table_name
|
|
10
15
|
@foreign_key = foreign_key
|
|
11
16
|
@join_table_name = join_table_name
|
|
12
17
|
@primary_key = primary_key
|
|
13
18
|
@where_clauses = where_clauses
|
|
19
|
+
@base_where_clauses = base_where_clauses
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
def to_h
|
|
@@ -23,6 +29,9 @@ module Exwiw
|
|
|
23
29
|
if where_clauses.size.positive?
|
|
24
30
|
hash[:where_clauses] = where_clauses.map { |wc| wc.is_a?(String) ? wc : wc.to_h }
|
|
25
31
|
end
|
|
32
|
+
if base_where_clauses.size.positive?
|
|
33
|
+
hash[:base_where_clauses] = base_where_clauses.map { |wc| wc.is_a?(String) ? wc : wc.to_h }
|
|
34
|
+
end
|
|
26
35
|
hash
|
|
27
36
|
end
|
|
28
37
|
end
|
|
@@ -54,8 +54,23 @@ module Exwiw
|
|
|
54
54
|
foreign_key: relation.foreign_key,
|
|
55
55
|
join_table_name: to_table.name,
|
|
56
56
|
primary_key: to_table.primary_key,
|
|
57
|
-
where_clauses: []
|
|
57
|
+
where_clauses: [],
|
|
58
|
+
base_where_clauses: []
|
|
58
59
|
)
|
|
60
|
+
|
|
61
|
+
# この hop 自体が polymorphic belongs_to の場合 (例: comments が
|
|
62
|
+
# commentable として posts へ polymorphic belongs_to)、型カラム
|
|
63
|
+
# (foreign_type) は結合元テーブル (from_table = base_table_name) 側に
|
|
64
|
+
# 存在する。外部キーだけでは reviewable_id=1 のような値が別モデルの
|
|
65
|
+
# 行と衝突しうるため、base_where_clauses に型条件を追加して結合元
|
|
66
|
+
# テーブルを絞り込む。
|
|
67
|
+
if relation.polymorphic?
|
|
68
|
+
join_clause.base_where_clauses.push QueryAst::WhereClause.new(
|
|
69
|
+
column_name: relation.foreign_type,
|
|
70
|
+
operator: :eq,
|
|
71
|
+
value: [relation.type_value]
|
|
72
|
+
)
|
|
73
|
+
end
|
|
59
74
|
relation_to_dump_target = to_table.belongs_to(dump_target.table_name)
|
|
60
75
|
if relation_to_dump_target
|
|
61
76
|
join_clause.where_clauses.push QueryAst::WhereClause.new(
|
|
@@ -63,6 +78,18 @@ module Exwiw
|
|
|
63
78
|
operator: :eq,
|
|
64
79
|
value: dump_target.ids
|
|
65
80
|
)
|
|
81
|
+
|
|
82
|
+
# 中間テーブルが dump target へ polymorphic belongs_to している場合は、
|
|
83
|
+
# 型カラム (foreign_type) も join 条件に追加する。型カラムは to_table
|
|
84
|
+
# (= join_table_name) 上に存在するため、JoinClause の where_clauses が
|
|
85
|
+
# join_table_name に対してコンパイルされる仕組みにそのまま乗せられる。
|
|
86
|
+
if relation_to_dump_target.polymorphic?
|
|
87
|
+
join_clause.where_clauses.push QueryAst::WhereClause.new(
|
|
88
|
+
column_name: relation_to_dump_target.foreign_type,
|
|
89
|
+
operator: :eq,
|
|
90
|
+
value: [relation_to_dump_target.type_value]
|
|
91
|
+
)
|
|
92
|
+
end
|
|
66
93
|
end
|
|
67
94
|
|
|
68
95
|
# Add filter from intermediate table to join clause
|
|
@@ -98,6 +125,17 @@ module Exwiw
|
|
|
98
125
|
value: dump_target.ids
|
|
99
126
|
)
|
|
100
127
|
|
|
128
|
+
# polymorphic belongs_to の場合は外部キーだけでは型を区別できないため
|
|
129
|
+
# (例: reviewable_id=1 が Product なのか別モデルなのか判別できない)、
|
|
130
|
+
# 型カラム (foreign_type) を type_value で絞り込む条件を追加する。
|
|
131
|
+
if belongs_to.polymorphic?
|
|
132
|
+
clauses.push Exwiw::QueryAst::WhereClause.new(
|
|
133
|
+
column_name: belongs_to.foreign_type,
|
|
134
|
+
operator: :eq,
|
|
135
|
+
value: [belongs_to.type_value]
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
101
139
|
if table.filter
|
|
102
140
|
clauses.push table.filter
|
|
103
141
|
end
|
|
@@ -76,12 +76,31 @@ module Exwiw
|
|
|
76
76
|
private def build_tables_for(models, conn)
|
|
77
77
|
tables_from_models = models.group_by(&:table_name).map do |table_name, model_group|
|
|
78
78
|
representative = model_group.first
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
primary_key = representative.primary_key
|
|
80
|
+
|
|
81
|
+
# 複合主キー (`representative.primary_key` が Array) のテーブルは現状未対応。
|
|
82
|
+
# primary_key を省略し、type で非対応である旨を明示したうえで skip:true を
|
|
83
|
+
# 付与して出力する。type を付けておくことで将来対応する際の目印になる。
|
|
84
|
+
# 利用者が必要に応じて手動で skip を外して設定し直せるよう、設定ファイル
|
|
85
|
+
# 自体は生成しておく。
|
|
86
|
+
if primary_key.is_a?(Array)
|
|
87
|
+
TableConfig.from_symbol_keys(
|
|
88
|
+
name: table_name,
|
|
89
|
+
type: TableConfig::UNSUPPORTED_COMPOSITE_PRIMARY_KEY,
|
|
90
|
+
skip: true,
|
|
91
|
+
comment: "exwiw does not support composite primary keys " \
|
|
92
|
+
"(#{primary_key.join(', ')}); data extraction is skipped.",
|
|
93
|
+
belongs_tos: aggregate_belongs_tos(model_group),
|
|
94
|
+
columns: representative.column_names.map { |name| { name: name } },
|
|
95
|
+
)
|
|
96
|
+
else
|
|
97
|
+
TableConfig.from_symbol_keys(
|
|
98
|
+
name: table_name,
|
|
99
|
+
primary_key: primary_key,
|
|
100
|
+
belongs_tos: aggregate_belongs_tos(model_group),
|
|
101
|
+
columns: representative.column_names.map { |name| { name: name } },
|
|
102
|
+
)
|
|
103
|
+
end
|
|
85
104
|
end
|
|
86
105
|
|
|
87
106
|
tables_from_models + build_rails_managed_tables(conn)
|
|
@@ -126,15 +145,44 @@ module Exwiw
|
|
|
126
145
|
end
|
|
127
146
|
|
|
128
147
|
private def aggregate_belongs_tos(models)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
.
|
|
133
|
-
.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
belongs_to_assocs = models.flat_map { |m| m.reflect_on_all_associations(:belongs_to) }
|
|
149
|
+
|
|
150
|
+
non_polymorphic = belongs_to_assocs
|
|
151
|
+
.reject(&:polymorphic?)
|
|
152
|
+
.map { |assoc| { table_name: assoc.table_name, foreign_key: assoc.foreign_key } }
|
|
153
|
+
|
|
154
|
+
# polymorphic な belongs_to (`belongs_to :reviewable, polymorphic: true`) は
|
|
155
|
+
# 単一の対象テーブルを持たない。対象になりうるテーブルは、他モデルで
|
|
156
|
+
# `has_many/has_one ..., as: <association_name>` と宣言されている側から逆引き
|
|
157
|
+
# する。各候補テーブルごとに、型カラム (`foreign_type`) と格納される型の値
|
|
158
|
+
# (`type_value`) を添えた belongs_to を 1 件ずつ展開する。
|
|
159
|
+
polymorphic = belongs_to_assocs
|
|
160
|
+
.select(&:polymorphic?)
|
|
161
|
+
.flat_map do |assoc|
|
|
162
|
+
polymorphic_target_models(assoc.name).map do |target_model|
|
|
163
|
+
{
|
|
164
|
+
table_name: target_model.table_name,
|
|
165
|
+
foreign_key: assoc.foreign_key,
|
|
166
|
+
foreign_type: assoc.foreign_type,
|
|
167
|
+
type_value: target_model.polymorphic_name,
|
|
168
|
+
}
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
(non_polymorphic + polymorphic).uniq
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# polymorphic 関連 `association_name` の対象となりうる具象モデルを、全モデルの
|
|
176
|
+
# `has_many` / `has_one` の `as:` オプションから逆引きして列挙する。
|
|
177
|
+
# `concrete_models` の並びは `ActiveRecord::Base.descendants` の順に依存し、
|
|
178
|
+
# Ruby バージョンによって変わりうるため、生成される belongs_to の並びが安定する
|
|
179
|
+
# よう `table_name` でソートして決定的に返す。
|
|
180
|
+
private def polymorphic_target_models(association_name)
|
|
181
|
+
concrete_models.select do |model|
|
|
182
|
+
(model.reflect_on_all_associations(:has_many) +
|
|
183
|
+
model.reflect_on_all_associations(:has_one))
|
|
184
|
+
.any? { |reflection| reflection.options[:as] == association_name }
|
|
185
|
+
end.sort_by(&:table_name)
|
|
138
186
|
end
|
|
139
187
|
|
|
140
188
|
# Identifies which database a model belongs to. With Rails multi-DB
|
data/lib/exwiw/table_config.rb
CHANGED
|
@@ -11,6 +11,11 @@ module Exwiw
|
|
|
11
11
|
RAILS_MANAGED_INTERNAL_METADATA,
|
|
12
12
|
].freeze
|
|
13
13
|
|
|
14
|
+
# exwiw が現状サポートしていない複合主キーのテーブルを表す type。
|
|
15
|
+
# schema:generate が skip:true と併せて付与する。将来サポートする際の
|
|
16
|
+
# 目印になるよう、rails-managed とは異なり columns/belongs_tos は保持する。
|
|
17
|
+
UNSUPPORTED_COMPOSITE_PRIMARY_KEY = "unsupported_composite_primary_key"
|
|
18
|
+
|
|
14
19
|
attribute :name, String
|
|
15
20
|
attribute :primary_key, optional(String), skip_serializing_if_nil: true
|
|
16
21
|
attribute :type, optional(String), skip_serializing_if_nil: true
|
|
@@ -138,7 +143,9 @@ module Exwiw
|
|
|
138
143
|
"Table '#{name}' has type=#{type}; columns must not be defined."
|
|
139
144
|
end
|
|
140
145
|
else
|
|
141
|
-
|
|
146
|
+
# skip:true のテーブルはデータ抽出を行わないため primary_key を要求しない。
|
|
147
|
+
# (例: exwiw 非対応の複合主キーテーブル)
|
|
148
|
+
if primary_key.nil? && !skip
|
|
142
149
|
raise ArgumentError, "Table '#{name}' requires primary_key."
|
|
143
150
|
end
|
|
144
151
|
end
|
data/lib/exwiw/version.rb
CHANGED