-
Notifications
You must be signed in to change notification settings - Fork 21.8k
Make it possible to override the implicit order column #34480
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
Make it possible to override the implicit order column #34480
Conversation
(@rails-bot has picked a reviewer for you, use r? to override) |
r? @eileencodes |
Example: | ||
|
||
class Project < ActiveRecord::Base | ||
self.implicit_order_column = "created_at" |
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.
Do you think it would be clearer to use UUID as the example column? Like we talked about at RubyConf created_at
might not be a good column for someone to use because there could be duplicates.
What would happen in the case there are duplicates? Can we make sure that Rails orders first by the set column and second by the ID if that column isn't a unique value?
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.
The particular use case for this feature is when the primary key itself is a UIID, and thus first/last return records in a seemingly arbitrary fashion because they aren’t sequential so I’m not sure if that would make things clearer. In such a case you would be able to specify the created_at as the implicit order column and thus get results that are closer to what happens with numeric incrementing primary keys. As you point out though, this would mean potentially inconsistent records being returned if the column isn’t unique and there are two with the same value. Adding a secondary sort by the primary key is one option to ensure the same record is always returned. The other alternative is to accept that if the user specifies a non-unique column they do so at their own risk. I prefer the later to keep things simple, but I’m happy to work up a patch that also includes the primary key as a secondary sort. What do you think?
# order clause is used. Defaults to the primary key. | ||
def implicit_order_column | ||
@implicit_order_column ||= primary_key | ||
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.
Instead of doing this as a getter / setter, could we just let subclasses override?
For example, the implementation in active record is:
class ActiveRecord::Base
def self.implicit_order_column
primary_key
end
end
If you want to override it in your subclass, just do:
class Project < ActiveRecord::Base
def self.implicit_order_column
"created_at"
end
end
That way we don't have to deal with ivar / cache management.
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 think class_attribute
would be better: it should be as simple as the override version, but is much more accessible for e.g. a future deprecation.
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 did look into class_attribute
but it wasn't immediately obvious how I'd set the default value to primary_key
.
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.
Hmmm.. I guess you'd set it to nil
, then ||
it in the caller
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.
@eileencodes / @tenderlove any thoughts on this? should I update to use class_attribute
instead?
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.
@tekin yes, make this a class attribute and then we'll merge it. 😊
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.
Updated to use class_attribute
.
0e3792c
to
d1cc1e1
Compare
PR updated to use an overridable class method instead of getter/setter. |
2bd52db
to
9a8f5c8
Compare
When calling ordered finder methods such as +first+ or +last+ without an explicit order clause, ActiveRecord sorts records by primary key. This can result in unpredictable and surprising behaviour when the primary key is not an auto-incrementing integer, for example when it's a UUID. This change makes it possible to override the column used for implicit ordering such that +first+ and +last+ will return more predictable results. For Example: class Project < ActiveRecord::Base self.implicit_order_column = "created_at" end
9a8f5c8
to
3b9982a
Compare
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.
Looks good, thanks 👍
🎉 |
Make it possible to override the implicit order column rails/rails#34480
Make it possible to override the implicit order column rails/rails#34480
One of the downsides to using UUIDs for primary keys is that we lose the implicit ordering behaviour of the `first` and `last` finder methods, which by default return the first and last records based on the primary key as a proxy for creation date. When that is an incrementing integer, things work as expected, but with UUIDs we end up with entirely random records based on the string ordering of the UUIDs. Rails 6 introduced the ability to override this implicit order column[1], which means we can set it to created_at and get back the natural and less surprising behaviour of `first` and `last`. [1]: rails/rails#34480
Add a matcher that can test the new [implicit_order_column][1] class property that is available on ActiveRecord classes in Rails 6. [1]: rails/rails#34480 Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
Add a matcher that can test the new [implicit_order_column][1] class property that is available on ActiveRecord classes in Rails 6. [1]: rails/rails#34480 Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
This fixes the Rubocop offense: Rails/OrderById See: https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsorderbyid But also... it helps with test fixtures. Test fixtures have erratic IDs, that come from a hash of the fixture name. So, we can't rely on sorting them by ID. So this threw me over the hump towards fixing this. I hadn't previously known about being able to set `self.implicit_order_column = "created_at"` on ActiveRecord Models, and this makes it a lot better than it used to be. So I'm happy to give this a try again now. See: https://stackoverflow.com/a/71469664/171183 and: rails/rails#34480 NOTE: To solve this properly, I had to go back and add indexes on the `created_at` columns for our 3 ActiveRecord models. So, you'll either need to add an index manually or recreate the database after this point.
This fixes the Rubocop offense: Rails/OrderById See: https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsorderbyid But also... it helps with test fixtures. Test fixtures have erratic IDs, that come from a hash of the fixture name. So, we can't rely on sorting them by ID. So this threw me over the hump towards fixing this. I hadn't previously known about being able to set `self.implicit_order_column = "created_at"` on ActiveRecord Models, and this makes it a lot better than it used to be. So I'm happy to give this a try again now. See: https://stackoverflow.com/a/71469664/171183 and: rails/rails#34480 NOTE: To solve this properly, I had to go back and add indexes on the `created_at` columns for our 3 ActiveRecord models. So, you'll either need to add an index manually or recreate the database after this point.
When calling ordered finder methods such as
first
orlast
without an explicit order clause, ActiveRecord sorts records by primary key. This can result in unpredictable and surprising behaviour when the primary key is not an an auto-incrementing integer, for example when it's a UUID. This change makes it possible to override the column used for implicit ordering such thatfirst
andlast
will return more predictable results.Example:
This takes a different approach to #34236, which automatically switches to using
created_at
if the primary key is a UUID and acreated_at
column exists.