exwiw 0.3.9 → 0.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea6a0b4fda446d66766027139136320937a59319bfe28d83ee595cf062a4023a
4
- data.tar.gz: 1ce3d4a7601ae6b3f1fe6ca8ed22e977389ac7ab46973c33113206911893326e
3
+ metadata.gz: a743c4759cdb8b292e4fdfe8ea3d4e10fa66e15a70677dd685366fb9e26a376b
4
+ data.tar.gz: '04048ceca802c0d418495cdf7c002bd47047beb05a53655a451c2c77a4d8270d'
5
5
  SHA512:
6
- metadata.gz: '08ffcb1713110629652b4047b159613ab3d13e5fffcd092433282f28564328d1609572d8f159b1c0e7fddfab1be4fc92651b8f819ddfa44a913e18da04e4aeef'
7
- data.tar.gz: 77529a1897ded0943898f60d8a55d54bfdf35f80fc512b57be7300adad9fc8c99767199de8e6d9fb9649dc2644fbcfc2252e445de2f5c38bc20aece5ae4a56d5
6
+ metadata.gz: 7d7a5ffca1cdae405e88fac5d72e4ed4e52ee3deec8cce0e503dcdc2b4136ec03ca2d84893c5897a3e56a78b0fa9e11dddc5f606566f32c90297815c8ade4702
7
+ data.tar.gz: 8e7742f720c371b41679c311d6323f8182e4f414f1ff199ac90928026ce46c2607a61a3c3a7ed91c23c97ea4b855d838bfee38b593d69c06f1dc743a17916eb0
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.4.1] - 2026-06-04
6
+
7
+ ### Added
8
+
9
+ - `MongoidSchemaGenerator` gains an opt-in `skip_unsupported:` mode (via the rake task, `EXWIW_SKIP_UNSUPPORTED=1 bundle exec rake exwiw:schema:generate_mongoid`). When enabled, generation no longer aborts on a construct exwiw cannot represent: an unresolvable `belongs_to` (whose target class no longer exists — e.g. a stale relation left behind after the model was removed) is skipped with a stderr warning while its foreign-key column is still kept as a field, and a polymorphic / self-referential-cyclic / unresolvable-parent `embedded_in` collection is emitted as a top-level `ignore: true` config annotated with a `comment` explaining why — so it is not wrongly dumped as its own collection — instead of raising. Off by default, so the existing fail-loud behavior is unchanged for callers that do not opt in. `MongodbCollectionConfig` now also carries an optional collection-level `comment` attribute, preserved across regeneration like the field / belongs_to `comment`.
10
+
11
+ ## [0.4.0] - 2026-06-04
12
+
13
+ ### Fixed
14
+
15
+ - PostgreSQL: extraction no longer fails with `operator does not exist: character varying = uuid` when a `belongs_to` chain crosses a varchar foreign key and a uuid primary key. The adapter now introspects column types via `pg_attribute`/`pg_type` at query compile time and injects `::text` casts on both sides of the comparison when a uuid/varchar mismatch is detected. Covers JOIN ON conditions, WHERE IN subqueries, and bulk-delete IN clauses. ([#73](https://github.com/heyinc/exwiw/pull/73))
16
+
5
17
  ## [0.3.9] - 2026-06-03
6
18
 
7
19
  ### Added
data/README.md CHANGED
@@ -200,6 +200,15 @@ Models in an inheritance hierarchy whose subclasses share the base's collection
200
200
 
201
201
  Regeneration preserves hand-edited `replace_with`, `filter`, `ignore`, and `bulk_insert_chunk_size` values, like the ActiveRecord generator. Indexes are not written to the config — they are introspected from the live database at dump time (see [MongoDB notes](#mongodb-notes)). Polymorphic `belongs_to` is not yet expanded by this task.
202
202
 
203
+ By default the task **aborts** when a model uses a construct exwiw cannot represent: a `belongs_to` whose target class can no longer be resolved (a stale relation left behind after its model was removed), or a polymorphic / self-referential-cyclic / unresolvable-parent `embedded_in` (see the cases above). Set `EXWIW_SKIP_UNSUPPORTED=1` to keep going instead:
204
+
205
+ ```bash
206
+ EXWIW_SKIP_UNSUPPORTED=1 bundle exec rake exwiw:schema:generate_mongoid
207
+ ```
208
+
209
+ - An unresolvable `belongs_to` is dropped from the collection's `belongs_tos` (its foreign-key column is still kept as an ordinary field, like the polymorphic / HABTM cases) and a warning naming the relation is printed to stderr.
210
+ - An unrepresentable `embedded_in` collection is emitted as a **top-level** config marked `"ignore": true` with a `comment` recording why, and a warning is printed. `ignore: true` keeps it out of extraction so it is not wrongly dumped as its own collection; to actually dump/mask such an embedded collection, define its `embedded_in` config by hand (see [Embedded documents](#embedded-documents)). This is useful for bootstrapping a config against a large app where a handful of legacy/polymorphic collections would otherwise block the whole run.
211
+
203
212
  ### Configuration
204
213
 
205
214
  This is an example of the one table schema:
@@ -177,8 +177,17 @@ module Exwiw
177
177
  end
178
178
 
179
179
  foreign_key = first_join.foreign_key
180
- subquery_sql = compile_ast(subquery_ast)
181
- sql += "\nWHERE #{select_query_ast.from_table_name}.#{foreign_key} IN (#{subquery_sql})"
180
+ outer_table = select_query_ast.from_table_name
181
+ inner_table = first_join.join_table_name
182
+ inner_column = first_join.primary_key
183
+ cast_to = types_need_cast?(
184
+ column_pg_type(outer_table, foreign_key),
185
+ column_pg_type(inner_table, inner_column)
186
+ ) ? 'text' : nil
187
+ subquery_sql = compile_ast(subquery_ast, select_cast_to: cast_to)
188
+ outer_expr = "#{outer_table}.#{foreign_key}"
189
+ outer_expr = "#{outer_expr}::text" if cast_to
190
+ sql += "\nWHERE #{outer_expr} IN (#{subquery_sql})"
182
191
 
183
192
  # first_join.base_where_clauses holds conditions on the outer
184
193
  # delete-target table (from_table_name), such as a polymorphic type
@@ -195,19 +204,30 @@ module Exwiw
195
204
  sql
196
205
  end
197
206
 
198
- def compile_ast(query_ast)
207
+ def compile_ast(query_ast, select_cast_to: nil)
199
208
  raise NotImplementedError unless query_ast.is_a?(Exwiw::QueryAst::Select)
200
209
 
201
210
  sql = "SELECT "
202
211
  sql += if query_ast.select_all
203
212
  "*"
204
213
  else
205
- query_ast.columns.map { |col| compile_column_name(query_ast, col) }.join(', ')
214
+ cols = query_ast.columns.map { |col| compile_column_name(query_ast, col) }
215
+ cols = cols.map { |c| "#{c}::#{select_cast_to}" } if select_cast_to
216
+ cols.join(', ')
206
217
  end
207
218
  sql += " FROM #{query_ast.from_table_name}"
208
219
 
209
220
  query_ast.join_clauses.each do |join|
210
- sql += " JOIN #{join.join_table_name} ON #{join.base_table_name}.#{join.foreign_key} = #{join.join_table_name}.#{join.primary_key}"
221
+ fk_expr = "#{join.base_table_name}.#{join.foreign_key}"
222
+ pk_expr = "#{join.join_table_name}.#{join.primary_key}"
223
+ if types_need_cast?(
224
+ column_pg_type(join.base_table_name, join.foreign_key),
225
+ column_pg_type(join.join_table_name, join.primary_key)
226
+ )
227
+ fk_expr = "#{fk_expr}::text"
228
+ pk_expr = "#{pk_expr}::text"
229
+ end
230
+ sql += " JOIN #{join.join_table_name} ON #{fk_expr} = #{pk_expr}"
211
231
 
212
232
  join.where_clauses.each do |where|
213
233
  compiled_where_condition = compile_where_condition(where, join.join_table_name)
@@ -246,23 +266,54 @@ module Exwiw
246
266
  "#{key} IN (#{values.join(', ')})"
247
267
  end
248
268
  elsif where_clause.operator == :in_subquery
249
- "#{key} IN (#{compile_subquery(where_clause.value)})"
269
+ subquery_sql = compile_subquery(where_clause.value, outer_table: table_name, outer_column: where_clause.column_name)
270
+ cast_to = subquery_cast_to(where_clause.value, table_name, where_clause.column_name)
271
+ outer_key = cast_to ? "#{key}::#{cast_to}" : key
272
+ "#{outer_key} IN (#{subquery_sql})"
250
273
  else
251
274
  raise "Unsupported operator: #{where_clause.operator}"
252
275
  end
253
276
  end
254
277
 
255
- private def compile_subquery(subquery)
256
- # A SelectSubquery wraps a full Select (the referencing table's
257
- # extraction query, projected to a foreign key); compile it as-is.
258
- return compile_ast(subquery.query) if subquery.is_a?(Exwiw::QueryAst::SelectSubquery)
278
+ private def compile_subquery(subquery, outer_table: nil, outer_column: nil)
279
+ cast_to = subquery_cast_to(subquery, outer_table, outer_column)
280
+
281
+ if subquery.is_a?(Exwiw::QueryAst::SelectSubquery)
282
+ return compile_ast(subquery.query, select_cast_to: cast_to)
283
+ end
259
284
 
260
285
  inner_values = subquery.where_values.map { |v| escape_value(v) }
261
- "SELECT #{subquery.table_name}.#{subquery.select_column} " \
286
+ select_expr = "#{subquery.table_name}.#{subquery.select_column}"
287
+ select_expr = "#{select_expr}::#{cast_to}" if cast_to
288
+ "SELECT #{select_expr} " \
262
289
  "FROM #{subquery.table_name} " \
263
290
  "WHERE #{subquery.table_name}.#{subquery.where_column} IN (#{inner_values.join(', ')})"
264
291
  end
265
292
 
293
+ private def subquery_select_target(subquery)
294
+ case subquery
295
+ when Exwiw::QueryAst::SelectSubquery
296
+ q = subquery.query
297
+ col = q.columns.first
298
+ col ? [q.from_table_name, col.name] : [nil, nil]
299
+ when Exwiw::QueryAst::Subquery
300
+ [subquery.table_name, subquery.select_column]
301
+ else
302
+ [nil, nil]
303
+ end
304
+ end
305
+
306
+ private def subquery_cast_to(subquery, outer_table, outer_column)
307
+ return nil if outer_table.nil? || outer_column.nil?
308
+
309
+ inner_table, inner_column = subquery_select_target(subquery)
310
+ return nil if inner_table.nil?
311
+
312
+ outer_type = column_pg_type(outer_table, outer_column)
313
+ inner_type = column_pg_type(inner_table, inner_column)
314
+ types_need_cast?(outer_type, inner_type) ? 'text' : nil
315
+ end
316
+
266
317
  private def escape_value(value)
267
318
  case value
268
319
  when nil
@@ -351,6 +402,36 @@ module Exwiw
351
402
  end
352
403
  end
353
404
 
405
+ private def column_pg_type(table_name, column_name)
406
+ @column_type_cache ||= {}
407
+ cache_key = [table_name, column_name]
408
+ return @column_type_cache[cache_key] if @column_type_cache.key?(cache_key)
409
+
410
+ sql = <<~SQL
411
+ SELECT t.typname
412
+ FROM pg_attribute a
413
+ JOIN pg_class c ON c.oid = a.attrelid
414
+ JOIN pg_type t ON t.oid = a.atttypid
415
+ WHERE c.relname = $1
416
+ AND a.attname = $2
417
+ AND a.attnum > 0
418
+ AND NOT a.attisdropped
419
+ LIMIT 1
420
+ SQL
421
+
422
+ result = connection.exec_params(sql, [table_name, column_name])
423
+ @column_type_cache[cache_key] = result.ntuples > 0 ? result.getvalue(0, 0) : nil
424
+ end
425
+
426
+ private def types_need_cast?(type_a, type_b)
427
+ return false if type_a.nil? || type_b.nil?
428
+ return false if type_a == type_b
429
+
430
+ string_types = %w[varchar text bpchar name].freeze
431
+ (type_a == 'uuid' && string_types.include?(type_b)) ||
432
+ (type_b == 'uuid' && string_types.include?(type_a))
433
+ end
434
+
354
435
  private def connection
355
436
  @connection ||=
356
437
  begin
@@ -14,6 +14,12 @@ module Exwiw
14
14
  attribute :fields, array(MongodbField)
15
15
  attribute :bulk_insert_chunk_size, optional(Integer), skip_serializing_if_nil: true
16
16
  attribute :ignore, Serdes::OptionalType.new(Serdes::ConcreteType.new(Boolean)), skip_serializing_if_nil: true
17
+ # Free-form note. Purely informational — exwiw never reads it — and preserved
18
+ # across `MongoidSchemaGenerator` regeneration like the field / belongs_to
19
+ # `comment`. The generator also emits one when, under `skip_unsupported`, it
20
+ # marks an unrepresentable collection `ignore: true`, to record why extraction
21
+ # was skipped.
22
+ attribute :comment, optional(String), skip_serializing_if_nil: true
17
23
 
18
24
  # Marks this config as physically embedded inside another collection's
19
25
  # documents. When set, this config is not processed as a standalone dump
@@ -62,6 +68,10 @@ module Exwiw
62
68
  merged.filter = filter
63
69
  merged.bulk_insert_chunk_size = bulk_insert_chunk_size
64
70
  merged.ignore = ignore
71
+ # A freshly generated comment (e.g. the skip_unsupported marker) wins so
72
+ # it stays accurate; otherwise a hand-added note on a normal collection
73
+ # is kept.
74
+ merged.comment = passed.comment || comment
65
75
  merged.embedded_in = passed.embedded_in
66
76
 
67
77
  # Structural facts of each belongs_to come from the freshly generated
@@ -14,14 +14,38 @@ module Exwiw
14
14
  # (`fields`, `relations`, `collection_name`), so it does not require a live
15
15
  # MongoDB connection.
16
16
  class MongoidSchemaGenerator
17
- def self.from_rails_application(output_dir:)
17
+ # Raised when an embedded collection's `embedded_in` cannot be expressed as
18
+ # an exwiw config (polymorphic embedding, self-referential/cyclic embedding,
19
+ # or an unresolvable embedding-parent class). A subclass of ArgumentError so
20
+ # the historical `raise_error(ArgumentError, ...)` contract is preserved.
21
+ # Under `skip_unsupported` the generator rescues this and emits an
22
+ # `ignore: true` config instead of aborting the whole run.
23
+ class UnsupportedEmbedding < ArgumentError
24
+ # A concise phrase (as opposed to the long, actionable exception message)
25
+ # recorded as the generated config's `comment`.
26
+ attr_reader :reason
27
+
28
+ def initialize(message, reason:)
29
+ super(message)
30
+ @reason = reason
31
+ end
32
+ end
33
+
34
+ # `skip_unsupported`: when true, the generator does not abort on a construct
35
+ # it cannot represent. It skips an unresolvable `belongs_to` (keeping the
36
+ # foreign-key field) and emits an unrepresentable embedded collection as an
37
+ # `ignore: true` top-level config annotated with a `comment`, warning to
38
+ # stderr in both cases. Off by default, so the historical fail-loud behavior
39
+ # is unchanged unless a caller opts in.
40
+ def self.from_rails_application(output_dir:, skip_unsupported: false)
18
41
  Rails.application.eager_load!
19
- new(models: ::Mongoid.models, output_dir: output_dir)
42
+ new(models: ::Mongoid.models, output_dir: output_dir, skip_unsupported: skip_unsupported)
20
43
  end
21
44
 
22
- def initialize(models:, output_dir:)
45
+ def initialize(models:, output_dir:, skip_unsupported: false)
23
46
  @models = models
24
47
  @output_dir = output_dir
48
+ @skip_unsupported = skip_unsupported
25
49
  end
26
50
 
27
51
  def generate!
@@ -78,7 +102,31 @@ module Exwiw
78
102
  # supported (MongodbCollectionConfig rejects them), so embedded configs
79
103
  # always carry an empty belongs_tos and instead declare where they live.
80
104
  attrs[:belongs_tos] = []
81
- attrs[:embedded_in] = embedded_in_for(ordered.find(&:embedded?))
105
+ begin
106
+ attrs[:embedded_in] = embedded_in_for(ordered.find(&:embedded?))
107
+ rescue => e
108
+ # Known-unrepresentable shapes arrive as UnsupportedEmbedding (with a
109
+ # concise reason). Without skip_unsupported, re-raise so the historical
110
+ # fail-loud behavior is preserved. The broad rescue is a deliberate
111
+ # safety net for skip_unsupported (a best-effort bootstrapping mode):
112
+ # any other error while deriving the embedding is turned into an
113
+ # `ignore: true` config too, so a single odd model never aborts the run.
114
+ raise e unless @skip_unsupported
115
+
116
+ reason =
117
+ if e.is_a?(UnsupportedEmbedding)
118
+ e.reason
119
+ else
120
+ "raised #{e.class} while deriving embedded_in (#{e.message.lines.first&.strip})"
121
+ end
122
+
123
+ # Emit the collection as a top-level config marked `ignore: true` so it
124
+ # is NOT (wrongly) dumped as its own collection, and record why. The
125
+ # user can hand-write its embedded_in config later to dump/mask it.
126
+ warn("exwiw: skip_unsupported: '#{collection_name}' #{reason}; emitting ignore:true (define embedded_in by hand to dump/mask it).")
127
+ attrs[:ignore] = true
128
+ attrs[:comment] = "exwiw could not derive embedded_in (#{reason}); marked ignore:true. Define this collection's embedded_in config by hand to dump/mask it."
129
+ end
82
130
  else
83
131
  attrs[:belongs_tos] = aggregate_belongs_tos(ordered)
84
132
  end
@@ -168,10 +216,25 @@ module Exwiw
168
216
  # same belongs_to twice, so uniq them.
169
217
  belongs_to_assocs
170
218
  .reject(&:polymorphic?)
171
- .map { |assoc| { table_name: assoc.klass.collection_name.to_s, foreign_key: assoc.foreign_key } }
219
+ .filter_map { |assoc| belongs_to_for(assoc) }
172
220
  .uniq
173
221
  end
174
222
 
223
+ # Resolves a referenced belongs_to to a `{ table_name, foreign_key }` pair.
224
+ # `assoc.klass` raises NameError when the association's target class no longer
225
+ # exists (a stale/legacy `belongs_to`, e.g. pointing at a model removed years
226
+ # ago). Under `skip_unsupported` such a relation is skipped with a warning —
227
+ # its foreign-key column is still tracked as an ordinary field by
228
+ # `aggregate_fields`, mirroring how polymorphic / HABTM relations are dropped.
229
+ private def belongs_to_for(assoc)
230
+ { table_name: assoc.klass.collection_name.to_s, foreign_key: assoc.foreign_key }
231
+ rescue NameError, ::Mongoid::Errors::MongoidError => e
232
+ raise e unless @skip_unsupported
233
+
234
+ warn("exwiw: skip_unsupported: skipping belongs_to ':#{assoc.name}' that could not be resolved (#{e.class}: #{e.message.lines.first&.strip}); its foreign key '#{assoc.foreign_key}' is still kept as a field.")
235
+ nil
236
+ end
237
+
175
238
  # Resolves the `embedded_in` config for an embedded model. Each embedded
176
239
  # model points at its *immediate* embedding parent: the parent's collection
177
240
  # name plus the single document key (`store_as`, defaulting to the relation
@@ -196,14 +259,30 @@ module Exwiw
196
259
  # names exactly one parent collection + path, so this shape cannot be
197
260
  # represented; fail loudly with an actionable message instead of crashing.
198
261
  if assoc.polymorphic?
199
- raise ArgumentError,
200
- "MongoidSchemaGenerator: '#{model.name}' (collection '#{model.collection_name}') " \
201
- "declares a polymorphic `embedded_in :#{assoc.name}`, which has no single embedding " \
202
- "parent collection and cannot be expressed as an exwiw `embedded_in` config. " \
203
- "Define the collection's config by hand, or make the relation non-polymorphic."
262
+ raise UnsupportedEmbedding.new(
263
+ "MongoidSchemaGenerator: '#{model.name}' (collection '#{model.collection_name}') " \
264
+ "declares a polymorphic `embedded_in :#{assoc.name}`, which has no single embedding " \
265
+ "parent collection and cannot be expressed as an exwiw `embedded_in` config. " \
266
+ "Define the collection's config by hand, or make the relation non-polymorphic.",
267
+ reason: "has a polymorphic embedded_in :#{assoc.name}",
268
+ )
204
269
  end
205
270
 
206
- parent = assoc.klass
271
+ parent =
272
+ begin
273
+ assoc.klass
274
+ rescue NameError => e
275
+ # The embedding-parent class named by `class_name` (or inferred from
276
+ # the relation) does not exist — a stale/renamed parent. exwiw cannot
277
+ # name a parent collection it cannot resolve.
278
+ raise UnsupportedEmbedding.new(
279
+ "MongoidSchemaGenerator: '#{model.name}' (collection '#{model.collection_name}') " \
280
+ "declares `embedded_in :#{assoc.name}` whose parent class cannot be resolved " \
281
+ "(#{e.message.lines.first&.strip}). Fix the association's class_name, or define the " \
282
+ "collection's config by hand.",
283
+ reason: "has an embedded_in :#{assoc.name} whose parent class is unresolvable",
284
+ )
285
+ end
207
286
 
208
287
  # A self-referential / cyclic `embedded_in` — Mongoid's
209
288
  # `recursively_embeds_many` / `recursively_embeds_one` (which declare a
@@ -216,19 +295,47 @@ module Exwiw
216
295
  # `MongodbAdapter#dumpable?` (`!embedded?`) would silently never dump the
217
296
  # collection's root documents. Fail loudly instead.
218
297
  if parent.collection_name.to_s == model.collection_name.to_s
219
- raise ArgumentError,
220
- "MongoidSchemaGenerator: '#{model.name}' (collection '#{model.collection_name}') " \
221
- "declares a self-referential (cyclic) `embedded_in :#{assoc.name}` that embeds the " \
222
- "collection inside documents of its own type (e.g. `recursively_embeds_many` / " \
223
- "`recursively_embeds_one`). " \
224
- "exwiw represents a collection as either top-level or embedded, not both, so this " \
225
- "cannot be expressed as an exwiw `embedded_in` config. Define the collection's config " \
226
- "by hand."
298
+ raise UnsupportedEmbedding.new(
299
+ "MongoidSchemaGenerator: '#{model.name}' (collection '#{model.collection_name}') " \
300
+ "declares a self-referential (cyclic) `embedded_in :#{assoc.name}` that embeds the " \
301
+ "collection inside documents of its own type (e.g. `recursively_embeds_many` / " \
302
+ "`recursively_embeds_one`). " \
303
+ "exwiw represents a collection as either top-level or embedded, not both, so this " \
304
+ "cannot be expressed as an exwiw `embedded_in` config. Define the collection's config " \
305
+ "by hand.",
306
+ reason: "has a self-referential (cyclic) embedded_in :#{assoc.name}",
307
+ )
227
308
  end
228
309
 
229
310
  # `store_as` defaults to the relation name and is the actual document key
230
311
  # the subdocuments are stored under inside the immediate parent.
231
- parent_relation = parent.relations[assoc.inverse.to_s]
312
+ parent_relation =
313
+ begin
314
+ parent.relations[assoc.inverse.to_s]
315
+ rescue ::Mongoid::Errors::MongoidError, NameError => e
316
+ # e.g. AmbiguousRelationship: the embedded class is embedded under
317
+ # several document keys in the parent (or otherwise has no single
318
+ # resolvable inverse), so exwiw cannot pick the one path it lives under.
319
+ raise UnsupportedEmbedding.new(
320
+ "MongoidSchemaGenerator: '#{model.name}' (collection '#{model.collection_name}') " \
321
+ "declares `embedded_in :#{assoc.name}` whose inverse on '#{parent.name}' is ambiguous " \
322
+ "or unresolvable (#{e.class}: #{e.message.lines.first&.strip}). Add an `inverse_of:` to " \
323
+ "disambiguate, or define the collection's config by hand.",
324
+ reason: "has an embedded_in :#{assoc.name} with an ambiguous/unresolvable inverse",
325
+ )
326
+ end
327
+
328
+ unless parent_relation
329
+ # `assoc.inverse` resolved to a name that is not an association on the
330
+ # parent (or to nothing), so there is no document key to embed under.
331
+ raise UnsupportedEmbedding.new(
332
+ "MongoidSchemaGenerator: '#{model.name}' (collection '#{model.collection_name}') " \
333
+ "declares `embedded_in :#{assoc.name}` but its inverse relation could not be located on " \
334
+ "'#{parent.name}' (the embedding document key is indeterminable). Add an `inverse_of:`, or " \
335
+ "define the collection's config by hand.",
336
+ reason: "has an embedded_in :#{assoc.name} whose inverse relation could not be located",
337
+ )
338
+ end
232
339
 
233
340
  { collection_name: parent.collection_name.to_s, path: parent_relation.store_as }
234
341
  end
data/lib/exwiw/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Exwiw
4
- VERSION = "0.3.9"
4
+ VERSION = "0.4.1"
5
5
  end
data/lib/tasks/exwiw.rake CHANGED
@@ -32,11 +32,17 @@ namespace :exwiw do
32
32
  end
33
33
 
34
34
  desc "Generate schema from a Mongoid application"
35
+ # Set EXWIW_SKIP_UNSUPPORTED=1 to keep generation going past constructs exwiw
36
+ # cannot represent (an unresolvable `belongs_to`, or a polymorphic / cyclic /
37
+ # unresolvable `embedded_in`): the unresolvable belongs_to is skipped and an
38
+ # unrepresentable embedded collection is emitted as `ignore: true` with a
39
+ # `comment`, each warned to stderr, instead of aborting the whole run.
35
40
  task generate_mongoid: :environment do
36
41
  require "exwiw"
37
42
 
38
43
  Exwiw::MongoidSchemaGenerator.from_rails_application(
39
44
  output_dir: ENV["OUTPUT_DIR_PATH"] || "exwiw",
45
+ skip_unsupported: ENV["EXWIW_SKIP_UNSUPPORTED"] == "1",
40
46
  ).generate!
41
47
  end
42
48
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exwiw
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.9
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shia