Context
Follow-up from #370 / #378.
Collection#fetch_associations's :has_one branch only guards against publishing a broken ManyToManySchema when the relation is polymorphic (is_polymorphic || source_polymorphic). A non-polymorphic has_one :through falls into the pre-existing OneToOneSchema path instead, regardless of what the source association actually is:
add_field(
association.name.to_s,
ForestAdminDatasourceToolkit::Schema::Relations::OneToOneSchema.new(
foreign_collection: format_model_name(association.klass.name),
origin_key: association.klass.primary_key,
origin_key_target: @model.primary_key
)
)
This branch never reads association.source_reflection at all -- origin_key/origin_key_target are unconditionally both models' own primary keys, whether the source is a belongs_to, a has_one, or anything else. Same class of bug #378 fixes for ManyToManySchema (a silently wrong foreign.id = origin.id join), just via a different schema type, and not gated on source type the way #370's bug was.
Repro
Supplier has_one :account; has_one :account_history, through: :account in packages/forest_admin_datasource_active_record/spec/dummy/app/models/supplier.rb. Note Account belongs_to :account_history -- the source here IS a belongs_to, which is exactly why this bug is broader than #370's: it doesn't matter what the source is, this branch always emits the two endpoints' own PKs regardless.
The existing spec at collection_spec.rb ('add has_one_through relation as a to-one (OneToOne)') currently pins this as expected behavior:
expect(field.origin_key).to eq(AccountHistory.primary_key)
expect(field.origin_key_target).to eq(Supplier.primary_key)
# -> account_histories.id = suppliers.id
Per @christophebrun-forest's review on #378: Supplier#account_history is actually a representable relation if built correctly -- through Account, with origin_key: accounts.supplier_id and foreign_key: accounts.account_history_id -- it's not inherently inexpressible, it's just mis-emitted by the current code path.
Suggested direction
Rebuild the non-polymorphic has_one :through branch to route through the intermediate model's own FK columns (mirroring what #378 does for the many-to-many shape) instead of unconditionally falling back to both endpoints' primary keys.
🤖 Generated with Claude Code
Context
Follow-up from #370 / #378.
Collection#fetch_associations's:has_onebranch only guards against publishing a brokenManyToManySchemawhen the relation is polymorphic (is_polymorphic || source_polymorphic). A non-polymorphichas_one :throughfalls into the pre-existingOneToOneSchemapath instead, regardless of what the source association actually is:This branch never reads
association.source_reflectionat all --origin_key/origin_key_targetare unconditionally both models' own primary keys, whether the source is abelongs_to, ahas_one, or anything else. Same class of bug #378 fixes forManyToManySchema(a silently wrongforeign.id = origin.idjoin), just via a different schema type, and not gated on source type the way #370's bug was.Repro
Supplier has_one :account; has_one :account_history, through: :accountinpackages/forest_admin_datasource_active_record/spec/dummy/app/models/supplier.rb. NoteAccount belongs_to :account_history-- the source here IS abelongs_to, which is exactly why this bug is broader than #370's: it doesn't matter what the source is, this branch always emits the two endpoints' own PKs regardless.The existing spec at
collection_spec.rb('add has_one_through relation as a to-one (OneToOne)') currently pins this as expected behavior:Per @christophebrun-forest's review on #378:
Supplier#account_historyis actually a representable relation if built correctly -- throughAccount, withorigin_key: accounts.supplier_idandforeign_key: accounts.account_history_id-- it's not inherently inexpressible, it's just mis-emitted by the current code path.Suggested direction
Rebuild the non-polymorphic
has_one :throughbranch to route through the intermediate model's own FK columns (mirroring what #378 does for the many-to-many shape) instead of unconditionally falling back to both endpoints' primary keys.🤖 Generated with Claude Code