Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow accepts_nested_attributes_for to accept associated record attributes with custom primary keys #48733

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

fatkodima
Copy link
Member

Fixes #48714.

Currently, when we have an accepts_nested_attributes_for for a child relation with a custom primary key, when assigning child attributes we must use "id" key with a value to provide a primary key, which can be confusing (as in the linked issue).

Example:

class Owner < ApplicationRecord
  has_many :pets
  accepts_nested_attributes_for :pets
end

class Pet < ApplicationRecord
  self.primary_key = :pet_id # custom primary key name
  belongs_to :owner
end

Before:

owner.update(pets_attributes: { id: 2, name: "parrot" }) # works, because we used "id" as a key
owner.update(pets_attributes: { pet_id: 2, name: "parrot" }) # does not work

After - both variants are working

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.

I just wanted to mention that changes in this PR treat primary_key value as a single column though technically Rails main allows primary_key to be derived and configured as an Array.
However, I don't think we need to overcomplicate this PR with any changes related to composite primary keys since, we can always do a follow up

Comment on lines 574 to 577
nested_attributes_possible_id_names(klass).each do |primary_key|
return attributes[primary_key] if attributes[primary_key].present?
end
nil
Copy link
Contributor

Choose a reason for hiding this comment

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

For some reason I really want to use find here but not sure if it looks any better

Suggested change
nested_attributes_possible_id_names(klass).each do |primary_key|
return attributes[primary_key] if attributes[primary_key].present?
end
nil
pk = nested_attributes_possible_id_names(klass).find do |primary_key|
attributes[primary_key]
end
attributes[pk] if pk

assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)

elsif attributes["id"].present?
raise_nested_attributes_record_not_found!(association_name, attributes["id"])
elsif id = get_nested_attributes_id(attributes, association.klass)
Copy link
Contributor

Choose a reason for hiding this comment

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

Unlikely it will be noticeable in the big picture but perhaps we could avoid calling get_nested_attributes_id more than once and just derive it before all the if statements and reuse it

unless call_reject_if(association_name, attributes)
# Make sure we are operating on the actual object which is in the association's
# proxy_target array (either by finding it, or adding it if not found)
# Take into account that the proxy_target may have changed due to callbacks
target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s }
target_record = association.target.detect { |record| record.id.to_s == get_nested_attributes_id(attributes, association.klass).to_s }
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as the other comment. It look like we should be able to perform get_nested_attributes_id(attributes, association.klass).to_s once and reuse it

Calling it multiple times makes me a little uncomfortable as it calls into nested_attributes_possible_id_names which allocates the same ["id", :id] array

nil
end

def nested_attributes_possible_id_names(klass)
Copy link
Contributor

Choose a reason for hiding this comment

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

If we won't be able to avoid multiple calls to get_nested_attributes_id perhaps we should consider memoizing the value

@fatkodima fatkodima force-pushed the accepts_nested_attributes_for-custom-pk branch from 253ae65 to be7d40c Compare July 13, 2023 18:01
@fatkodima
Copy link
Member Author

Thanks! Updated with the suggestion for each -> find and extracted getting id in both methods.

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.

👍 Let's have someone from the core team to have a look

One thing I didn't mention in my previous review is that in this PR we treat primary_key value as a single column while on main Active Record allows primary_key to be derived as an array. However, I don't think it is a concern as we shouldn't be doing two things (composite primary keys support & bug fix) in the same PR. So to me it's okay to ship it as is and we will address the composite primary keys support later

Thank you for the fix!

@westonganger
Copy link
Contributor

westonganger commented Dec 6, 2023

Seems like this fix is similar/interrelated to the changes in another PR #48390

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.

accepts_nested_attributes_for does not work with non id primary_key
3 participants