Skip to content

Conversation

gmcgibbon
Copy link
Member

Motivation / Background

This Pull Request has been created because belongs_to associations with composite primary keys are broken.

Detail

Raises ActiveRecord::CompositePrimaryKeyMismatchError when belongs_to foreign key and primary key don't have the same length.

Additional information

This fixes an issue where autosave fails to update foreign key attributes here:

primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
association_id = record._read_attribute(primary_key)
self[foreign_key] = association_id unless self[foreign_key] == association_id
end

I'm not sure if my fix is the best way to fix this problem, there might be an easier way around this than introducing an error and additional validation check. From my perspective, if a model has a composite primary key, its associations need query constraints to spell out the foreign key columns Active Record should use. Let me know what you think!

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one change. Changes that are unrelated should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

Copy link
Contributor

@nvasilevski nvasilevski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to be very handy to avoid ambiguous error messages when defining associations from/to a model with a composite primary key. Thanks!

I have a suspicion that the validation check can eventually be moved to the parent class so we check the condition for any type of the association. However I'm okay if we start from belongs_to and consider expanding it once we have another failing test-case

def check_validity!
super

if !polymorphic? && klass.composite_primary_key? && Array(association_primary_key).length != Array(foreign_key).length
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation is true for any type of the model so klass.composite_primary_key? check is not necessary from the design point of view. However we can kind of use it to prevent unnecessary arrays allocation for non-cpk models. So it's up to you too keep it

Suggested change
if !polymorphic? && klass.composite_primary_key? && Array(association_primary_key).length != Array(foreign_key).length
if !polymorphic? && Array(association_primary_key).length != Array(foreign_key).length

If we are going to keep it I think we designed association_primary_key and foreign_key to be an array if the klass is a cpk model, so potentially we should be able to do:

Suggested change
if !polymorphic? && klass.composite_primary_key? && Array(association_primary_key).length != Array(foreign_key).length
if !polymorphic? && klass.composite_primary_key? && association_primary_key.length != foreign_key.length

though I could be missing something

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe removing the composite PK check makes the tests for query_constraints that use models under Sharded:: fail. In this case, query_constraints don't necessarily map to FKs AFAICT.

Copy link
Contributor

@nvasilevski nvasilevski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to be very handy to avoid ambiguous error messages when defining associations from/to a model with a composite primary key. Thanks!

I have a suspicion that the validation check can eventually be moved to the parent class so we check the condition for any type of the association. However I'm okay if we start from belongs_to and consider expanding it once we have another failing test-case


def initialize(reflection = nil)
if reflection
@reflection = reflection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't appear to be necessary, where is this used?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't, but a lot of inverse and association validity errors seem to have that accessor and ivar for some reason. I'll remove it 👍

@gmcgibbon gmcgibbon force-pushed the belongs_to_cpk branch 3 times, most recently from 6aa85e1 to 22fcbac Compare June 2, 2023 04:07
Raise ActiveRecord::CompositePrimaryKeyMismatchError when a belongs_to,
has_one, or has_many foreign key and primary key don't have the same length.
@eileencodes eileencodes merged commit 254f1d8 into rails:main Jun 2, 2023
@gmcgibbon gmcgibbon deleted the belongs_to_cpk branch June 2, 2023 16:01
@gmcgibbon gmcgibbon changed the title Add composite primary key validity check on belongs_to associations. Add composite primary key validity check on belongs_to, has_one, and has_many associations. Jun 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants