forest_admin_datasource_active_record 1.39.4 → 1.40.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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b924a7fc6ab865fa83285d4dc554e0d2124b26c2b998d6c06c3dcfcd357d313d
|
|
4
|
+
data.tar.gz: dfd941d070ddb69f934b43db627d83ed0dac8af12fcf3dbf5f6594bdfaae617d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 13177d23d5702e9a93d282e09c213c8b99fb2d5ba47828712e3fc67b84a198e67bf65848af806ce58f084c8465bda80ad282c29da73b0413a3e50aac6b38bf0c
|
|
7
|
+
data.tar.gz: 297df24c7a0e70780552f7f0b4712860171930f57cce1c0a7a602732e73ace9ce187e41dde2e8e9048c5796c7674b8a23dd077895bcb05b64cdb96de6e4bc456
|
|
@@ -88,6 +88,69 @@ module ForestAdminDatasourceActiveRecord
|
|
|
88
88
|
%i[destroy destroy_async delete_all].include?(association.options[:dependent])
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
+
# join_foreign_key can fall back to the through model's own PK (see
|
|
92
|
+
# valid_many_to_many_source? below); that PK usually still exists as a column, so
|
|
93
|
+
# "column missing" alone doesn't catch every unrepresentable case -- a composite key is
|
|
94
|
+
# unrepresentable too, even when every individual column exists: GeneratorField looks
|
|
95
|
+
# fields up by the raw key, so a composite key never resolves. A composite key can reach
|
|
96
|
+
# us either as a real Array, or (via an explicit primary_key: option on the source)
|
|
97
|
+
# already flattened by Rails into a mangled String like '["a", "b"]' -- neither is ever a
|
|
98
|
+
# real column name, so checking column membership catches both forms uniformly, on
|
|
99
|
+
# whichever collection each key is actually resolved against downstream (#370).
|
|
100
|
+
def unrepresentable_many_to_many?(association, through_reflection)
|
|
101
|
+
[
|
|
102
|
+
[association.join_foreign_key, through_reflection.klass],
|
|
103
|
+
[through_reflection.foreign_key, through_reflection.klass],
|
|
104
|
+
[association.association_primary_key, association.klass]
|
|
105
|
+
].any? { |key, klass| !klass.column_names.include?(key) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# True only for a belongs_to source: ThroughReflection#join_foreign_key delegates to
|
|
109
|
+
# the source reflection, and only belongs_to overrides it to return a real FK column.
|
|
110
|
+
# Any other source macro falls back to AssociationReflection#active_record_primary_key
|
|
111
|
+
# -- the through model's own primary key column (honoring a custom primary_key:, and
|
|
112
|
+
# possibly composite), not a genuine join column (#370).
|
|
113
|
+
def valid_many_to_many_source?(association)
|
|
114
|
+
association.source_reflection&.belongs_to?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Same criterion as unrepresentable_many_to_many?: a composite primary key can arrive as a
|
|
118
|
+
# real Array or already flattened by Rails into a mangled String -- checking column
|
|
119
|
+
# membership catches both forms, plus a custom primary_key pointing at a non-column, all of
|
|
120
|
+
# which would otherwise hit nil.column_type in build_one_to_one_schema (#379).
|
|
121
|
+
def unrepresentable_one_to_one?(association)
|
|
122
|
+
!association.klass.column_names.include?(association.klass.primary_key) ||
|
|
123
|
+
!@model.column_names.include?(@model.primary_key)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def build_many_to_many_field(association, through_reflection, is_polymorphic, source_polymorphic)
|
|
127
|
+
ForestAdminDatasourceToolkit::Schema::Relations::ManyToManySchema.new(
|
|
128
|
+
foreign_collection: format_model_name(association.klass.name),
|
|
129
|
+
origin_key: through_reflection.foreign_key,
|
|
130
|
+
origin_key_target: through_reflection.join_foreign_key,
|
|
131
|
+
foreign_key: association.join_foreign_key,
|
|
132
|
+
foreign_key_target: association.association_primary_key,
|
|
133
|
+
through_collection: format_model_name(through_reflection.klass.name),
|
|
134
|
+
origin_type_field: is_polymorphic ? through_reflection.type : nil,
|
|
135
|
+
origin_type_value: is_polymorphic ? @model.name : nil,
|
|
136
|
+
foreign_type_field: source_polymorphic ? association.source_reflection.foreign_type : nil,
|
|
137
|
+
foreign_type_value: source_polymorphic ? association.options[:source_type] : nil
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def add_many_to_many_field(association, through_reflection, is_polymorphic, source_polymorphic)
|
|
142
|
+
if unrepresentable_many_to_many?(association, through_reflection)
|
|
143
|
+
warn_unrepresentable_many_to_many(association, through_reflection)
|
|
144
|
+
return
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
add_field(
|
|
148
|
+
association.name.to_s,
|
|
149
|
+
build_many_to_many_field(association, through_reflection, is_polymorphic, source_polymorphic)
|
|
150
|
+
)
|
|
151
|
+
warn_identity_join(association, through_reflection) unless valid_many_to_many_source?(association)
|
|
152
|
+
end
|
|
153
|
+
|
|
91
154
|
# rubocop:disable Metrics/BlockNesting
|
|
92
155
|
def fetch_associations
|
|
93
156
|
associations(@model, support_polymorphic_relations: @support_polymorphic_relations).each do |association|
|
|
@@ -99,32 +162,23 @@ module ForestAdminDatasourceActiveRecord
|
|
|
99
162
|
is_polymorphic = through_reflection.options[:as].present?
|
|
100
163
|
source_polymorphic = association.source_reflection&.polymorphic? &&
|
|
101
164
|
association.options[:source_type].present?
|
|
165
|
+
many_to_many_shape = is_polymorphic || source_polymorphic
|
|
102
166
|
|
|
103
|
-
if
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
foreign_collection: format_model_name(association.klass.name),
|
|
108
|
-
origin_key: through_reflection.foreign_key,
|
|
109
|
-
origin_key_target: through_reflection.join_foreign_key,
|
|
110
|
-
foreign_key: association.join_foreign_key,
|
|
111
|
-
foreign_key_target: association.association_primary_key,
|
|
112
|
-
through_collection: format_model_name(through_reflection.klass.name),
|
|
113
|
-
origin_type_field: is_polymorphic ? through_reflection.type : nil,
|
|
114
|
-
origin_type_value: is_polymorphic ? @model.name : nil,
|
|
115
|
-
foreign_type_field: source_polymorphic ? association.source_reflection.foreign_type : nil,
|
|
116
|
-
foreign_type_value: source_polymorphic ? association.options[:source_type] : nil
|
|
117
|
-
)
|
|
118
|
-
)
|
|
167
|
+
if many_to_many_shape
|
|
168
|
+
add_many_to_many_field(association, through_reflection, is_polymorphic, source_polymorphic)
|
|
169
|
+
elsif unrepresentable_one_to_one?(association)
|
|
170
|
+
warn_unrepresentable_one_to_one(association, through_reflection)
|
|
119
171
|
else
|
|
120
172
|
add_field(
|
|
121
173
|
association.name.to_s,
|
|
122
174
|
ForestAdminDatasourceToolkit::Schema::Relations::OneToOneSchema.new(
|
|
123
175
|
foreign_collection: format_model_name(association.klass.name),
|
|
124
176
|
origin_key: association.klass.primary_key,
|
|
125
|
-
origin_key_target: @model.primary_key
|
|
177
|
+
origin_key_target: @model.primary_key,
|
|
178
|
+
is_read_only: true
|
|
126
179
|
)
|
|
127
180
|
)
|
|
181
|
+
warn_readonly_identity_join(association, through_reflection)
|
|
128
182
|
end
|
|
129
183
|
elsif association.inverse_of&.polymorphic?
|
|
130
184
|
add_field(
|
|
@@ -186,21 +240,7 @@ module ForestAdminDatasourceActiveRecord
|
|
|
186
240
|
source_polymorphic = association.source_reflection&.polymorphic? &&
|
|
187
241
|
association.options[:source_type].present?
|
|
188
242
|
|
|
189
|
-
|
|
190
|
-
association.name.to_s,
|
|
191
|
-
ForestAdminDatasourceToolkit::Schema::Relations::ManyToManySchema.new(
|
|
192
|
-
foreign_collection: format_model_name(association.klass.name),
|
|
193
|
-
origin_key: through_reflection.foreign_key,
|
|
194
|
-
origin_key_target: through_reflection.join_foreign_key,
|
|
195
|
-
foreign_key: association.join_foreign_key,
|
|
196
|
-
foreign_key_target: association.association_primary_key,
|
|
197
|
-
through_collection: format_model_name(through_reflection.klass.name),
|
|
198
|
-
origin_type_field: is_polymorphic ? through_reflection.type : nil,
|
|
199
|
-
origin_type_value: is_polymorphic ? @model.name : nil,
|
|
200
|
-
foreign_type_field: source_polymorphic ? association.source_reflection.foreign_type : nil,
|
|
201
|
-
foreign_type_value: source_polymorphic ? association.options[:source_type] : nil
|
|
202
|
-
)
|
|
203
|
-
)
|
|
243
|
+
add_many_to_many_field(association, through_reflection, is_polymorphic, source_polymorphic)
|
|
204
244
|
elsif association.inverse_of&.polymorphic?
|
|
205
245
|
add_field(
|
|
206
246
|
association.name.to_s,
|
|
@@ -303,6 +343,65 @@ module ForestAdminDatasourceActiveRecord
|
|
|
303
343
|
klass
|
|
304
344
|
end
|
|
305
345
|
|
|
346
|
+
def warn_unrepresentable_many_to_many(association, through_reflection)
|
|
347
|
+
logger = ActiveSupport::Logger.new($stdout)
|
|
348
|
+
logger.warn(
|
|
349
|
+
"[ForestAdmin] ⚠️ Skipping association '#{association.name}' in model '#{@model.name}': " \
|
|
350
|
+
'this relation cannot be represented as a Forest Admin many-to-many -- its join keys must ' \
|
|
351
|
+
'each be a single existing column, on the through collection ' \
|
|
352
|
+
"'#{format_model_name(through_reflection.klass.name)}' or the foreign collection " \
|
|
353
|
+
"'#{format_model_name(association.klass.name)}' (composite/query_constraints keys are " \
|
|
354
|
+
'not supported).'
|
|
355
|
+
)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# join_foreign_key/association_primary_key are guaranteed to be single, real, existing
|
|
359
|
+
# columns here: unrepresentable_many_to_many? above already checks column membership for
|
|
360
|
+
# both (and for origin_key) before this is ever called.
|
|
361
|
+
#
|
|
362
|
+
# Deliberately a diagnostic, not a deprecation notice: this shape is common (any
|
|
363
|
+
# has_many/has_one :through sourced from a plain has_one/has_many, not just polymorphic
|
|
364
|
+
# ones) and the relation is kept published to avoid narrowing the schema -- see #370.
|
|
365
|
+
# No removal is promised or planned.
|
|
366
|
+
def warn_identity_join(association, through_reflection)
|
|
367
|
+
logger = ActiveSupport::Logger.new($stdout)
|
|
368
|
+
logger.warn(
|
|
369
|
+
"[ForestAdmin] ⚠️ Association '#{association.name}' in model '#{@model.name}' is published " \
|
|
370
|
+
"as a many-to-many joining '#{format_model_name(through_reflection.klass.name)}'." \
|
|
371
|
+
"'#{association.join_foreign_key}' to " \
|
|
372
|
+
"'#{format_model_name(association.klass.name)}'.'#{association.association_primary_key}': " \
|
|
373
|
+
"its source association is not a belongs_to, so this join uses each side's own identifier " \
|
|
374
|
+
'rather than a dedicated foreign key column -- see #370 for background.'
|
|
375
|
+
)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def warn_unrepresentable_one_to_one(association, through_reflection)
|
|
379
|
+
logger = ActiveSupport::Logger.new($stdout)
|
|
380
|
+
logger.warn(
|
|
381
|
+
"[ForestAdmin] ⚠️ Skipping association '#{association.name}' in model '#{@model.name}': " \
|
|
382
|
+
"this has_one :through (via '#{format_model_name(through_reflection.klass.name)}') can't be " \
|
|
383
|
+
'represented as a Forest Admin one-to-one -- one of its endpoints has a composite primary key.'
|
|
384
|
+
)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# OneToOneSchema has no through_collection, so this publishes each side's own primary key
|
|
388
|
+
# as a placeholder join -- the columns exist on both sides, so GeneratorField's field
|
|
389
|
+
# lookups don't raise, but the join isn't meaningful data (it matches two unrelated
|
|
390
|
+
# sequences), which is exactly why it's marked read-only rather than left writable through
|
|
391
|
+
# what's really the foreign collection's own primary key (#379).
|
|
392
|
+
def warn_readonly_identity_join(association, through_reflection)
|
|
393
|
+
logger = ActiveSupport::Logger.new($stdout)
|
|
394
|
+
foreign_key = association.klass.primary_key
|
|
395
|
+
origin_key = @model.primary_key
|
|
396
|
+
logger.warn(
|
|
397
|
+
"[ForestAdmin] ⚠️ Association '#{association.name}' in model '#{@model.name}' is published " \
|
|
398
|
+
"as a read-only one-to-one joining '#{format_model_name(association.klass.name)}'.'#{foreign_key}' " \
|
|
399
|
+
"to this model's own '#{origin_key}': it's a has_one chained through " \
|
|
400
|
+
"'#{format_model_name(through_reflection.klass.name)}', which OneToOneSchema can't express as a " \
|
|
401
|
+
'real two-hop join -- see #379 for background.'
|
|
402
|
+
)
|
|
403
|
+
end
|
|
404
|
+
|
|
306
405
|
def warn_missing_polymorphic_columns(association)
|
|
307
406
|
missing_columns = []
|
|
308
407
|
missing_columns << association.foreign_key unless schema[:fields][association.foreign_key]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: forest_admin_datasource_active_record
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.40.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthieu
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: exe
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-08-
|
|
12
|
+
date: 2026-08-28 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: activerecord
|