Skip to content

Non-polymorphic has_one :through always publishes a bogus identity join, regardless of source type #379

Description

@matthv

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions