exwiw 0.4.6 → 0.4.7
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 +11 -0
- data/README.md +1 -0
- data/lib/exwiw/adapter/mongodb_adapter.rb +84 -22
- data/lib/exwiw/adapter/postgresql_adapter.rb +13 -11
- data/lib/exwiw/belongs_to.rb +18 -2
- data/lib/exwiw/cli.rb +25 -5
- data/lib/exwiw/mongodb_collection_config.rb +4 -2
- data/lib/exwiw/mongoid_schema_generator.rb +12 -2
- data/lib/exwiw/table_config.rb +5 -2
- data/lib/exwiw/version.rb +1 -1
- data/lib/exwiw.rb +5 -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: 6bf23b63e169e4f28dceda8611fe1dc599ed3fba7565a063ac11d7e66e84816e
|
|
4
|
+
data.tar.gz: 4768cb5990bc6bb534e38bf458d814ad7be4ff57cc4ecb02565aae245078a7d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd2b11ab17de73e205639bc917cdd4bff23a793caa282c23f1a987b3391a6bc1550e65078924f82c6e63bdc03c22a9e37b0b1977c2602f5fbe0940b68dd92c13
|
|
7
|
+
data.tar.gz: 78681a8c80263454d9bcaef29282de2bf1853b478dd2025382b2a0e4dfe089ea14af8dd970cd0127c8ddf2b71f9a3306024959303052102592203487d387e5a7
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.4.7] - 2026-06-10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- PostgreSQL: `CREATE TYPE ... AS ENUM` statements in schema dumps no longer duplicate labels when the same enum type is used by multiple columns across the target tables. The `query_enum_types` query now filters by column in a subquery so the `pg_type` / `pg_enum` join produces each label exactly once.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- MongoDB: a `belongs_to` may now declare `references`, the parent field its foreign key actually points at, so foreign-key propagation can follow a relation that references a non-`_id` parent field (e.g. a `uuid`). Previously the adapter always stashed the parent's `primary_key` (`_id`, an ObjectId) and constrained children with `foreign_key $in [ObjectId(...)]`; a child whose FK stores the parent's `uuid` String never matched, so its slice (and everything below it) came back empty — making business-entity-scoped dumps unusable for models referenced by `primary_key: :uuid`. With `"references": "uuid"` on the child's `belongs_to`, the adapter captures the parent's `uuid` values during extraction and matches the child against those instead. `references` defaults to the parent's `primary_key` (so existing configs are unchanged). `schema:generate_mongoid` now auto-emits it by reading Mongoid's `belongs_to ..., primary_key:` (a non-`_id` primary key becomes `"references": "<field>"`; a default `_id` emits nothing), so no hand-editing is needed for `primary_key:`-declared associations — and a hand-added value still survives regeneration. The SQL adapters ignore it (they join on the parent primary key).
|
|
14
|
+
- MongoDB: `--uri=URI` connects with a full connection string (`mongodb://...` or `mongodb+srv://...`), letting you reach managed/replica-set deployments (e.g. Atlas) where TLS, `replicaSet`, `authSource`, and credentials must be specified — they live in the URI's query string. Previously the adapter was hard-coded to a single `host:port` with no TLS / `authSource` / `replicaSet` / SRV support, so such instances were unreachable. When given, the URI is the source of truth: `--host`/`--port`/`--user`/`DATABASE_PASSWORD` are ignored and `--host`/`--port`/`--database` are no longer required; `--database`, if still passed, overrides the database in the URI path. The flag is mongodb-only, and the URI is never written to logs (it may carry credentials).
|
|
15
|
+
|
|
5
16
|
## [0.4.6] - 2026-06-08
|
|
6
17
|
|
|
7
18
|
### Fixed
|
data/README.md
CHANGED
|
@@ -494,6 +494,7 @@ The MongoDB adapter is experimental. To use it:
|
|
|
494
494
|
|
|
495
495
|
- Add `gem "mongo"` to your Gemfile in addition to `exwiw` (it is not declared as a runtime dependency of the gem).
|
|
496
496
|
- Set `--adapter=mongodb`. `--user` / `DATABASE_PASSWORD` are optional and only needed when your MongoDB requires authentication.
|
|
497
|
+
- `--uri=URI` connects with a full MongoDB connection string (`mongodb://...` or `mongodb+srv://...`) instead of `--host`/`--port`. Use it for managed/replica-set deployments (e.g. Atlas) where TLS, `replicaSet`, `authSource`, and credentials must be expressed — put them in the URI's query string (e.g. `mongodb+srv://user:pass@cluster.example.com/?authSource=admin&tls=true`). The URI takes precedence: when given, `--host`/`--port`/`--user`/`DATABASE_PASSWORD` are ignored, and `--host`/`--port`/`--database` are no longer required on the CLI. `--database`, if still passed, overrides the database in the URI path; otherwise the database from the URI is used. This flag is **mongodb-only** (the SQL adapters shell out to their own client binaries and have no equivalent). The URI may carry credentials, so it is never written to logs.
|
|
497
498
|
- The MongoDB adapter consumes a separate config type, `MongodbCollectionConfig`, with MongoDB-native naming. Use `fields` (instead of the SQL adapters' `columns`), and set `"primary_key": "_id"`. Foreign keys (`shop_id`, `user_id`, ...) stay as ordinary fields.
|
|
498
499
|
- `--ids` values are coerced to the type actually stored in `_id` before filtering: integer-looking ids become `Integer`, 24-char hex ids become `BSON::ObjectId` (Mongoid's default `_id` type — a plain String would never match an ObjectId), and any other string is left as-is.
|
|
499
500
|
- `--target-collection=COLLECTION` is a mongodb-only alias of `--target-table` (use whichever reads better for MongoDB). Specifying both, or using `--target-collection` with a non-mongodb adapter, is an error.
|
|
@@ -45,13 +45,20 @@ module Exwiw
|
|
|
45
45
|
# the same config.
|
|
46
46
|
@embedded_children_by_parent = index_embedded_children(config_by_name)
|
|
47
47
|
|
|
48
|
+
# Which of this collection's fields downstream children will `$in`-match
|
|
49
|
+
# against (always including primary_key). Stashed for the matching
|
|
50
|
+
# #execute call to capture, by the same build_query-before-execute
|
|
51
|
+
# invariant the embedded index relies on.
|
|
52
|
+
@propagation_keys = propagation_keys_for(config, config_by_name)
|
|
53
|
+
|
|
48
54
|
filter =
|
|
49
55
|
if config.name == dump_target.table_name
|
|
50
56
|
# `--ids-field` may override which field --ids is matched against;
|
|
51
57
|
# otherwise fall back to the primary key. Note this only changes the
|
|
52
58
|
# WHERE filter on the target collection — downstream foreign-key
|
|
53
|
-
# propagation
|
|
54
|
-
#
|
|
59
|
+
# propagation keys off each child belongs_to's `references` field
|
|
60
|
+
# (default: the parent primary_key); see #execute, which stashes
|
|
61
|
+
# those fields into @state.
|
|
55
62
|
#
|
|
56
63
|
# Type coercion is only applied to the primary key (`_id`), whose
|
|
57
64
|
# stored type we know (Mongoid's default ObjectId). For a custom
|
|
@@ -64,16 +71,15 @@ module Exwiw
|
|
|
64
71
|
{ config.primary_key => { "$in" => coerce_ids(dump_target.ids) } }
|
|
65
72
|
end
|
|
66
73
|
else
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
end
|
|
74
|
+
config.belongs_tos.each_with_object({}) do |relation, acc|
|
|
75
|
+
# Constrain by the parent field this FK actually references
|
|
76
|
+
# (`relation.references`, default the parent primary_key). The
|
|
77
|
+
# values were captured from that field's documents in #execute, so
|
|
78
|
+
# their BSON type already matches the stored FK — no coercion.
|
|
79
|
+
values = parent_state_for(relation, config_by_name)
|
|
80
|
+
next if values.nil? || values.empty?
|
|
81
|
+
|
|
82
|
+
acc[relation.foreign_key] = { "$in" => values }
|
|
77
83
|
end
|
|
78
84
|
end
|
|
79
85
|
|
|
@@ -81,7 +87,7 @@ module Exwiw
|
|
|
81
87
|
collection: config.name,
|
|
82
88
|
primary_key: config.primary_key,
|
|
83
89
|
filter: filter,
|
|
84
|
-
projection: build_projection(config),
|
|
90
|
+
projection: build_projection(config, @propagation_keys),
|
|
85
91
|
)
|
|
86
92
|
end
|
|
87
93
|
|
|
@@ -94,7 +100,14 @@ module Exwiw
|
|
|
94
100
|
.comment(query_comment_text("collection=#{query.collection}"))
|
|
95
101
|
.to_a
|
|
96
102
|
|
|
97
|
-
|
|
103
|
+
# Stash, per referenced field, the values children will `$in`-match
|
|
104
|
+
# against. @propagation_keys is set by the build_query call for this same
|
|
105
|
+
# collection; fall back to the primary key if execute is driven without a
|
|
106
|
+
# preceding build_query (e.g. in isolation from a test).
|
|
107
|
+
keys = @propagation_keys || [query.primary_key]
|
|
108
|
+
@state[query.collection] = keys.each_with_object({}) do |key, acc|
|
|
109
|
+
acc[key] = docs.map { |doc| doc[key] }
|
|
110
|
+
end
|
|
98
111
|
|
|
99
112
|
docs
|
|
100
113
|
end
|
|
@@ -227,7 +240,7 @@ module Exwiw
|
|
|
227
240
|
end
|
|
228
241
|
end
|
|
229
242
|
|
|
230
|
-
private def build_projection(config)
|
|
243
|
+
private def build_projection(config, propagation_keys = [config.primary_key])
|
|
231
244
|
projection = {}
|
|
232
245
|
# Always include primary key so masking templates referencing it work,
|
|
233
246
|
# even if it is not declared in fields.
|
|
@@ -240,9 +253,40 @@ module Exwiw
|
|
|
240
253
|
embedded_children_of(config).each do |child|
|
|
241
254
|
projection[child.embedded_in.path] = 1
|
|
242
255
|
end
|
|
256
|
+
# Ensure every field a child references is fetched, even one not declared
|
|
257
|
+
# in `fields` — otherwise doc[ref] would be nil and the child's $in empty.
|
|
258
|
+
propagation_keys.each { |key| projection[key] = 1 }
|
|
243
259
|
projection
|
|
244
260
|
end
|
|
245
261
|
|
|
262
|
+
# The distinct set of this collection's fields that downstream children
|
|
263
|
+
# constrain on (each child belongs_to's `references`, defaulting to this
|
|
264
|
+
# collection's primary_key), with primary_key always included so the
|
|
265
|
+
# historical primary-key-keyed propagation keeps working.
|
|
266
|
+
private def propagation_keys_for(config, config_by_name)
|
|
267
|
+
referenced = config_by_name.each_value.flat_map do |child|
|
|
268
|
+
next [] if child.embedded?
|
|
269
|
+
|
|
270
|
+
child.belongs_tos
|
|
271
|
+
.select { |relation| relation.table_name == config.name }
|
|
272
|
+
.map { |relation| relation.references || config.primary_key }
|
|
273
|
+
end
|
|
274
|
+
([config.primary_key] + referenced).uniq
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# The captured parent-collection values a child belongs_to should be
|
|
278
|
+
# constrained by: the values of the parent field the FK references
|
|
279
|
+
# (`relation.references`, default the parent primary_key). nil when the
|
|
280
|
+
# parent has not been executed yet.
|
|
281
|
+
private def parent_state_for(relation, config_by_name)
|
|
282
|
+
parent_fields = @state[relation.table_name]
|
|
283
|
+
return nil if parent_fields.nil?
|
|
284
|
+
|
|
285
|
+
reference_field =
|
|
286
|
+
relation.references || config_by_name.fetch(relation.table_name).primary_key
|
|
287
|
+
parent_fields[reference_field]
|
|
288
|
+
end
|
|
289
|
+
|
|
246
290
|
private def apply_replace_with!(doc, config)
|
|
247
291
|
config.fields.each do |field|
|
|
248
292
|
next unless field.replace_with
|
|
@@ -292,16 +336,34 @@ module Exwiw
|
|
|
292
336
|
@db ||=
|
|
293
337
|
begin
|
|
294
338
|
require 'mongo'
|
|
295
|
-
address = "#{@connection_config.host}:#{@connection_config.port}"
|
|
296
|
-
options = { database: @connection_config.database_name }
|
|
297
|
-
if @connection_config.user && !@connection_config.user.to_s.empty?
|
|
298
|
-
options[:user] = @connection_config.user
|
|
299
|
-
options[:password] = @connection_config.password
|
|
300
|
-
end
|
|
301
339
|
Mongo::Logger.logger.level = ::Logger::WARN
|
|
302
|
-
|
|
340
|
+
if uri_connection?
|
|
341
|
+
# A full connection URI (e.g. `mongodb+srv://...`) is the source of
|
|
342
|
+
# truth: TLS, replicaSet, authSource and credentials are read from
|
|
343
|
+
# it, so host/port/user/password are ignored. `--database`, if
|
|
344
|
+
# given, overrides the database in the URI path; otherwise the
|
|
345
|
+
# URI's own database is used. The URI is never logged (it may carry
|
|
346
|
+
# credentials).
|
|
347
|
+
client_options = {}
|
|
348
|
+
if @connection_config.database_name && !@connection_config.database_name.to_s.empty?
|
|
349
|
+
client_options[:database] = @connection_config.database_name
|
|
350
|
+
end
|
|
351
|
+
Mongo::Client.new(@connection_config.uri, **client_options)
|
|
352
|
+
else
|
|
353
|
+
address = "#{@connection_config.host}:#{@connection_config.port}"
|
|
354
|
+
options = { database: @connection_config.database_name }
|
|
355
|
+
if @connection_config.user && !@connection_config.user.to_s.empty?
|
|
356
|
+
options[:user] = @connection_config.user
|
|
357
|
+
options[:password] = @connection_config.password
|
|
358
|
+
end
|
|
359
|
+
Mongo::Client.new([address], **options)
|
|
360
|
+
end
|
|
303
361
|
end
|
|
304
362
|
end
|
|
363
|
+
|
|
364
|
+
private def uri_connection?
|
|
365
|
+
!@connection_config.uri.nil? && !@connection_config.uri.to_s.empty?
|
|
366
|
+
end
|
|
305
367
|
end
|
|
306
368
|
end
|
|
307
369
|
end
|
|
@@ -373,20 +373,22 @@ module Exwiw
|
|
|
373
373
|
|
|
374
374
|
placeholders = table_names.each_with_index.map { |_, i| "$#{i + 1}" }.join(', ')
|
|
375
375
|
sql = <<~SQL
|
|
376
|
-
SELECT
|
|
376
|
+
SELECT
|
|
377
377
|
n.nspname AS type_schema,
|
|
378
378
|
t.typname AS type_name,
|
|
379
379
|
array_agg(e.enumlabel ORDER BY e.enumsortorder) AS enum_labels
|
|
380
|
-
FROM
|
|
381
|
-
JOIN
|
|
382
|
-
JOIN
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
380
|
+
FROM pg_type t
|
|
381
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
382
|
+
JOIN pg_enum e ON e.enumtypid = t.oid
|
|
383
|
+
WHERE t.typtype = 'e'
|
|
384
|
+
AND t.oid IN (
|
|
385
|
+
SELECT a.atttypid
|
|
386
|
+
FROM pg_attribute a
|
|
387
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
388
|
+
WHERE c.relname IN (#{placeholders})
|
|
389
|
+
AND a.attnum > 0
|
|
390
|
+
AND NOT a.attisdropped
|
|
391
|
+
)
|
|
390
392
|
GROUP BY n.nspname, t.typname
|
|
391
393
|
ORDER BY n.nspname, t.typname
|
|
392
394
|
SQL
|
data/lib/exwiw/belongs_to.rb
CHANGED
|
@@ -12,6 +12,20 @@ module Exwiw
|
|
|
12
12
|
# non-polymorphic belongs_to.
|
|
13
13
|
attribute :foreign_type, optional(String), skip_serializing_if_nil: true
|
|
14
14
|
attribute :type_value, optional(String), skip_serializing_if_nil: true
|
|
15
|
+
# The field on the parent (`table_name`) that `foreign_key` actually points
|
|
16
|
+
# at. nil means the parent's `primary_key` (the usual `_id`), so existing
|
|
17
|
+
# configs behave exactly as before. Set it when the FK references a non-`_id`
|
|
18
|
+
# parent field — e.g. Mongoid's `belongs_to :foo, primary_key: :uuid`, where
|
|
19
|
+
# the child stores `foo.uuid` rather than `foo._id`.
|
|
20
|
+
#
|
|
21
|
+
# Only the MongodbAdapter consumes this (to stash and `$in`-match the right
|
|
22
|
+
# parent field during foreign-key propagation); the SQL adapters ignore it,
|
|
23
|
+
# since they join on the parent primary key. MongoidSchemaGenerator emits it
|
|
24
|
+
# automatically from Mongoid's `belongs_to ..., primary_key:` (only when that
|
|
25
|
+
# is non-`_id`); a value may also be hand-added, and either way it survives
|
|
26
|
+
# regeneration (see TableConfig#merge / MongodbCollectionConfig#merge), so a
|
|
27
|
+
# hand edit is not clobbered by re-introspection.
|
|
28
|
+
attribute :references, optional(String), skip_serializing_if_nil: true
|
|
15
29
|
# User-owned fields. The schema generators never emit them, but a user can
|
|
16
30
|
# add them by hand and they survive regeneration (see TableConfig#merge /
|
|
17
31
|
# MongodbCollectionConfig#merge). `ignore:true` drops the relation from
|
|
@@ -28,8 +42,10 @@ module Exwiw
|
|
|
28
42
|
end
|
|
29
43
|
|
|
30
44
|
# Structural identity used to match a freshly generated belongs_to against a
|
|
31
|
-
# user-maintained one during merge. `comment`/`ignore` are
|
|
32
|
-
#
|
|
45
|
+
# user-maintained one during merge. `comment`/`ignore`/`references` are
|
|
46
|
+
# intentionally excluded so a generated relation matches a hand-edited one
|
|
47
|
+
# regardless of whether either carries a `references`, letting the merge
|
|
48
|
+
# reconcile them (a hand-edited `references` wins over the generated one).
|
|
33
49
|
def identity
|
|
34
50
|
[table_name, foreign_key, foreign_type, type_value]
|
|
35
51
|
end
|
data/lib/exwiw/cli.rb
CHANGED
|
@@ -32,6 +32,7 @@ module Exwiw
|
|
|
32
32
|
@database_port = nil
|
|
33
33
|
@database_user = nil
|
|
34
34
|
@database_password = ENV["DATABASE_PASSWORD"]
|
|
35
|
+
@connection_uri = nil
|
|
35
36
|
@output_dir = nil
|
|
36
37
|
@config_dir = nil
|
|
37
38
|
@database_adapter = nil
|
|
@@ -64,6 +65,7 @@ module Exwiw
|
|
|
64
65
|
user: @database_user,
|
|
65
66
|
password: @database_password,
|
|
66
67
|
database_name: @database_name,
|
|
68
|
+
uri: @connection_uri,
|
|
67
69
|
)
|
|
68
70
|
|
|
69
71
|
dump_target = DumpTarget.new(
|
|
@@ -115,17 +117,21 @@ module Exwiw
|
|
|
115
117
|
|
|
116
118
|
resolve_target_collection_alias!
|
|
117
119
|
resolve_ids_column_alias!
|
|
120
|
+
resolve_uri_option!
|
|
118
121
|
|
|
119
122
|
if @subcommand == "explain"
|
|
120
123
|
validate_explain_only!
|
|
121
124
|
end
|
|
122
125
|
|
|
123
126
|
if @database_adapter != "sqlite"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
# When a connection URI is supplied (mongodb only), host/port/database
|
|
128
|
+
# are read from the URI, so none of them are required on the CLI.
|
|
129
|
+
required_options = {}
|
|
130
|
+
unless @connection_uri
|
|
131
|
+
required_options["Target database host"] = @database_host
|
|
132
|
+
required_options["Target database port"] = @database_port
|
|
133
|
+
required_options["Target database name"] = @database_name
|
|
134
|
+
end
|
|
129
135
|
required_options["Database user"] = @database_user unless @database_adapter == "mongodb"
|
|
130
136
|
required_options.each do |k, v|
|
|
131
137
|
if v.nil?
|
|
@@ -253,6 +259,19 @@ module Exwiw
|
|
|
253
259
|
end
|
|
254
260
|
end
|
|
255
261
|
|
|
262
|
+
# `--uri` supplies a full connection string (e.g. `mongodb+srv://...`) and is
|
|
263
|
+
# mongodb-only — the SQL adapters shell out to their own client binaries with
|
|
264
|
+
# discrete host/port/user flags and have no equivalent. Runs after the
|
|
265
|
+
# adapter name has been normalized so the family check is reliable.
|
|
266
|
+
private def resolve_uri_option!
|
|
267
|
+
return if @connection_uri.nil?
|
|
268
|
+
|
|
269
|
+
if @database_adapter != "mongodb"
|
|
270
|
+
$stderr.puts "--uri is only supported by the mongodb adapter (use --host/--port)"
|
|
271
|
+
exit 1
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
256
275
|
private def validate_explain_only!
|
|
257
276
|
if @database_adapter == "mongodb"
|
|
258
277
|
$stderr.puts "mongodb adapter is not yet supported by 'explain' subcommand"
|
|
@@ -354,6 +373,7 @@ module Exwiw
|
|
|
354
373
|
@config_dir = File.expand_path(v)
|
|
355
374
|
end
|
|
356
375
|
opts.on("-a", "--adapter=ADAPTER", "Database adapter: mysql, sqlite, postgresql, mongodb (aliases: mysql2, sqlite3)") { |v| @database_adapter = v }
|
|
376
|
+
opts.on("--uri=URI", "Full MongoDB connection URI (mongodb:// or mongodb+srv://). mongodb adapter only; takes precedence over --host/--port/--user. TLS, replicaSet, authSource and credentials are read from the URI.") { |v| @connection_uri = v }
|
|
357
377
|
opts.on("--database=DATABASE", "Target database name") { |v| @database_name = v }
|
|
358
378
|
opts.on("--target-table=[TABLE]", "Target table for extraction. If omitted, dump all tables.") { |v| @target_table_name = v }
|
|
359
379
|
opts.on("--target-collection=[COLLECTION]", "Alias of --target-table for the mongodb adapter.") { |v| @target_collection_name = v }
|
|
@@ -75,14 +75,16 @@ module Exwiw
|
|
|
75
75
|
merged.embedded_in = passed.embedded_in
|
|
76
76
|
|
|
77
77
|
# Structural facts of each belongs_to come from the freshly generated
|
|
78
|
-
# config
|
|
79
|
-
#
|
|
78
|
+
# config (including a generator-derived `references`), but the user-owned
|
|
79
|
+
# `comment`/`ignore` — and a hand-edited `references`, which overrides the
|
|
80
|
+
# generated one — carry over when the same relation still exists.
|
|
80
81
|
receiver_belongs_to_by_identity = belongs_tos.each_with_object({}) { |bt, h| h[bt.identity] = bt }
|
|
81
82
|
merged.belongs_tos = passed.belongs_tos.map do |pbt|
|
|
82
83
|
receiver_bt = receiver_belongs_to_by_identity[pbt.identity]
|
|
83
84
|
if receiver_bt
|
|
84
85
|
pbt.comment = receiver_bt.comment if receiver_bt.comment
|
|
85
86
|
pbt.ignore = receiver_bt.ignore unless receiver_bt.ignore.nil?
|
|
87
|
+
pbt.references = receiver_bt.references if receiver_bt.references
|
|
86
88
|
end
|
|
87
89
|
pbt
|
|
88
90
|
end
|
|
@@ -220,14 +220,24 @@ module Exwiw
|
|
|
220
220
|
.uniq
|
|
221
221
|
end
|
|
222
222
|
|
|
223
|
-
# Resolves a referenced belongs_to to a `{ table_name, foreign_key }` pair
|
|
223
|
+
# Resolves a referenced belongs_to to a `{ table_name, foreign_key }` pair
|
|
224
|
+
# (plus `references` when the FK points at a non-`_id` parent field).
|
|
224
225
|
# `assoc.klass` raises NameError when the association's target class no longer
|
|
225
226
|
# exists (a stale/legacy `belongs_to`, e.g. pointing at a model removed years
|
|
226
227
|
# ago). Under `skip_unsupported` such a relation is skipped with a warning —
|
|
227
228
|
# its foreign-key column is still tracked as an ordinary field by
|
|
228
229
|
# `aggregate_fields`, mirroring how polymorphic / HABTM relations are dropped.
|
|
229
230
|
private def belongs_to_for(assoc)
|
|
230
|
-
{ table_name: assoc.klass.collection_name.to_s, foreign_key: assoc.foreign_key }
|
|
231
|
+
result = { table_name: assoc.klass.collection_name.to_s, foreign_key: assoc.foreign_key }
|
|
232
|
+
# Mongoid's `belongs_to ..., primary_key: :uuid` makes the child's foreign
|
|
233
|
+
# key reference that parent field rather than the parent's `_id`. Surface
|
|
234
|
+
# it as `references` so MongodbAdapter constrains children by the right
|
|
235
|
+
# field (issue B1). Omit it for the default `_id` (the parent primary_key
|
|
236
|
+
# the generator emits) so existing configs/snapshots are unchanged. SQL
|
|
237
|
+
# adapters ignore `references`; only MongodbAdapter consumes it.
|
|
238
|
+
reference_field = assoc.primary_key.to_s
|
|
239
|
+
result[:references] = reference_field unless reference_field == "_id"
|
|
240
|
+
result
|
|
231
241
|
rescue NameError, ::Mongoid::Errors::MongoidError => e
|
|
232
242
|
raise e unless @skip_unsupported
|
|
233
243
|
|
data/lib/exwiw/table_config.rb
CHANGED
|
@@ -139,8 +139,10 @@ module Exwiw
|
|
|
139
139
|
merged_table.ignore = ignore
|
|
140
140
|
|
|
141
141
|
# Structural facts of each belongs_to come from the freshly generated
|
|
142
|
-
# config, but the user-owned `comment`/`ignore` carry over
|
|
143
|
-
# relation still exists.
|
|
142
|
+
# config, but the user-owned `comment`/`ignore`/`references` carry over
|
|
143
|
+
# when the same relation still exists. (`references` is only consumed by
|
|
144
|
+
# the MongodbAdapter, but it lives on the shared BelongsTo, so preserve
|
|
145
|
+
# it here too rather than silently dropping a hand-added value.)
|
|
144
146
|
receiver_belongs_to_by_identity = belongs_tos.each_with_object({}) { |bt, hash| hash[bt.identity] = bt }
|
|
145
147
|
merged_table.belongs_tos =
|
|
146
148
|
passed_table.belongs_tos.map do |passed_belongs_to|
|
|
@@ -148,6 +150,7 @@ module Exwiw
|
|
|
148
150
|
if receiver_belongs_to
|
|
149
151
|
passed_belongs_to.comment = receiver_belongs_to.comment if receiver_belongs_to.comment
|
|
150
152
|
passed_belongs_to.ignore = receiver_belongs_to.ignore unless receiver_belongs_to.ignore.nil?
|
|
153
|
+
passed_belongs_to.references = receiver_belongs_to.references if receiver_belongs_to.references
|
|
151
154
|
end
|
|
152
155
|
passed_belongs_to
|
|
153
156
|
end
|
data/lib/exwiw/version.rb
CHANGED
data/lib/exwiw.rb
CHANGED
|
@@ -40,5 +40,9 @@ module Exwiw
|
|
|
40
40
|
# the target table. When nil the table's primary key is used (the historical
|
|
41
41
|
# behavior). Currently only honored by the mongodb adapter.
|
|
42
42
|
DumpTarget = Struct.new(:table_name, :ids, :ids_field, keyword_init: true)
|
|
43
|
-
|
|
43
|
+
# `uri` is an optional full connection string (currently only honored by the
|
|
44
|
+
# mongodb adapter, e.g. `mongodb+srv://...`). When present it is the source of
|
|
45
|
+
# truth for the connection — host/port/user/password are ignored — so TLS,
|
|
46
|
+
# replica_set, auth_source, etc. can be expressed via the URI's query string.
|
|
47
|
+
ConnectionConfig = Struct.new(:adapter, :host, :port, :user, :password, :database_name, :uri, keyword_init: true)
|
|
44
48
|
end
|