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
Reset has_one
association when the related belongs_to
association is set
#43122
base: main
Are you sure you want to change the base?
Reset has_one
association when the related belongs_to
association is set
#43122
Conversation
activerecord/lib/active_record/associations/belongs_to_association.rb
Outdated
Show resolved
Hide resolved
Thanks for picking this up @intrip ! |
5aa30aa
to
f550780
Compare
@p8 I've handled your suggestion, thanks! |
Does / should this work for other inverse association types, e.g. |
@eugeneius We explicitly skip it for now: https://github.com/rails/rails/pull/42729/files#diff-ceff30ddab4e756e3a70ece45076eb17ff2f587a068dae657d2ad3a265a3f0d6 |
@@ -94,6 +95,12 @@ def replace(record) | |||
self.target = record | |||
end | |||
|
|||
def reset_inverse_instance(record) | |||
if record |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this if
still required when the method is always called when record
is present?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@p8 I think so because we call this method with reset_inverse_instance(target)
hence target
might be nil
even if record
is present.
I suppose that's unclear because we call the argument record
, maybe we should call it target
instead?
Actually we can even avoid the parameter since we already can retrieve target
without passing it...
What about:
def reset_target_inverse_instance
if target
inverse_association_for(target)&.reset
end
end
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'm not sure 🤔
I think it's clearest if we use the same method as for the elsif target
branch, and check for target
there.
if record
raise_on_type_mismatch!(record)
remove_inverse_instance(target) if target
set_inverse_instance(record)
@updated = true
elsif target
remove_inverse_instance(target)
Or maybe we should move the target
check to remove_inverse_instance
?
def remove_inverse_instance(record)
if record && inverse = inverse_association_for(record)
inverse.inversed_from(nil)
end
end
Than we can simplify to the elsif
to an else
as well:
if record
raise_on_type_mismatch!(record)
remove_inverse_instance(target)
set_inverse_instance(record)
@updated = true
else
remove_inverse_instance(target)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see it would make sense to remove the target argument, but we pass it for a lot of methods:
rails/activerecord/lib/active_record/associations/has_one_association.rb
Lines 100 to 101 in 18707ab
nullify_owner_attributes(target) | |
remove_inverse_instance(target) |
So for consistency I think it's better to keep it for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@p8 The problem of using the same method for both cases is that they don't do exactly the same thing:
remove_inverse_instance(target)
will set the inverse tonil
, the inverse is loaded and cached in memoryreset_inverse_instance(record)
resets the inverse association, which will be retrieved via a SQL query the next time is accessed
We could reset the inverse association in both cases but this leads to a little performance degradation because we might trigger a SQL query even when it's not required (the elsif
part).
I've just pushed a rename target => record
to reset_inverse_instance
method in order to make the code clearer.
We can do further refactors later on, if the current changes are acceptable; I hope is ok for you 🙂 .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@p8 pinging you incase the notification was unnoticed 🙇
… is set. When setting a `belongs_to` association the inverse `has_one` association is reset. Example: ```ruby jimmy = Author.create(name: "Jimmy Tolkien") david = Author.create(name: "DHH") post = jimmy.create_post(title: "The silly medallion", body: "") jimmy.post.update!(author: david) assert_nil jimmy.post ``` Fixes rails#43096
f550780
to
262c2ed
Compare
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. |
ping |
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. |
ping |
Is this still not merged? Why? |
I don't know sorry, @p8 let me know if you are still interested in this and I'll rebase main |
Summary
When setting a
belongs_to
association the inversehas_one
association is reset.Example:
Fixes #43096