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
Reversed attribute precedence in insert_all #44316
base: main
Are you sure you want to change the base?
Conversation
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 believe it's more surprising that the attributes explicitly passed in to insert_all don't ultimately take precedence; these seem to be the stronger indicator of intent, more even than the scope.
✅ I would agree!
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. |
Bump. Any further thoughts on this change? 😅 |
I think I agree that it makes sense to be able to override the scope, however if we were to change See #35638 (review), currently Because this would be a breaking change I'm sure it would have to go through a deprecation cycle as well (probably a config/framework default) |
7d8b686
to
c7075b2
Compare
Thanks for the great feedback, @skipkayhil. I took a closer look at how So I've modified the PR to refine the existing logic to duplicate Thanks again for the feedback. 🙂 Let me know if I'd need to tweak anything else with this PR. |
Summary
Rails 6.1 (via #38899) introduced incorporating relationship scopes into the attributes inserted when using
insert_all
/upsert_all
. This allowedauthor.books.insert_all({ title: "Out of the Silent Planet" })
to work as expected. Neat!However, the attributes from the scope were combined with attributes passed in as arguments with
merge!
. This meant that attributes on the scope would overwrite those passed in, which can be surprising in some cases, especially when using default scopes.The example in the test for attribute precedence prior to this PR was a bit contrived:
... but one could perhaps see the logic of it:
author.books
as the scope is a strong contender for what should win out when inserting the new record.However, I believe it's more surprising that the attributes explicitly passed in to
insert_all
don't ultimately take precedence; these seem to be the stronger indicator of intent, more even than the scope. A different example illustrates what I mean:In this example, books may be archived using a timestamp. By default, we only ever want to work with un-archived books, and so we've set up a
default_scope
to accomplish this (it could just as easily be a scope on a relationship as well, e.g., an Authorhas_many :books, ->{ where(archived_at: nil) }
). But should we want to insert a book as already archived, passing in a timestamp forarchived_at
is not enough! We also have to remember to also callunscope(where: :archived_at)
beforeinsert_all
. That's surprising.What I suggest is reversing the precedence and deferring to the attributes passed into the method over what we get from the scope (i.e., using
reverse_merge!
instead ofmerge!
); the attributes passed in are stronger signals of intent than the scope on which one callsinsert_all
.This would be a reversal from current behavior, though I don't believe the current behavior is noted in the documentation (cf. #41639). It would, however, make other attributes more consistent with the automatically-added timestamps, which already use
reverse_merge!
to defer to the passed-in attributes over the default values (cf. #43003).Thanks for your thoughts and consideration!