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

Reset has_one association when the related belongs_to association is set #43122

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

Conversation

intrip
Copy link
Contributor

@intrip intrip commented Aug 28, 2021

Summary

When setting a belongs_to association the inverse has_one association is reset.

Example:

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 #43096

@intrip intrip marked this pull request as ready for review August 28, 2021 13:16
@p8
Copy link
Member

p8 commented Aug 28, 2021

Thanks for picking this up @intrip !

@intrip intrip force-pushed the 43096-reset-has-one-when-belons-to-is-set branch from 5aa30aa to f550780 Compare August 28, 2021 20:02
@intrip
Copy link
Contributor Author

intrip commented Aug 28, 2021

@p8 I've handled your suggestion, thanks!

@eugeneius
Copy link
Member

Does / should this work for other inverse association types, e.g. has_many?

@p8
Copy link
Member

p8 commented Aug 30, 2021

@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
Copy link
Member

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?

Copy link
Contributor Author

@intrip intrip Aug 30, 2021

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

?

Copy link
Member

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)

Copy link
Member

@p8 p8 Sep 4, 2021

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:

nullify_owner_attributes(target)
remove_inverse_instance(target)

So for consistency I think it's better to keep it for now.

Copy link
Contributor Author

@intrip intrip Sep 13, 2021

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 to nil, the inverse is loaded and cached in memory
  • reset_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 🙂 .

Copy link
Contributor Author

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
@intrip intrip force-pushed the 43096-reset-has-one-when-belons-to-is-set branch from f550780 to 262c2ed Compare September 13, 2021 12:31
@rails-bot
Copy link

rails-bot bot commented Jan 4, 2022

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.
Thank you for your contributions.

@rails-bot rails-bot bot added the stale label Jan 4, 2022
@intrip
Copy link
Contributor Author

intrip commented Jan 4, 2022

ping

@rails-bot rails-bot bot removed the stale label Jan 4, 2022
@rails-bot
Copy link

rails-bot bot commented Apr 4, 2022

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.
Thank you for your contributions.

@rails-bot rails-bot bot added the stale label Apr 4, 2022
@intrip
Copy link
Contributor Author

intrip commented Apr 4, 2022

ping

@rails-bot rails-bot bot removed the stale label Apr 4, 2022
@xjia1
Copy link

xjia1 commented Nov 19, 2023

Is this still not merged? Why?

@intrip
Copy link
Contributor Author

intrip commented Nov 20, 2023

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

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.

Kind of caching issue updating has_one association
4 participants