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 |
There was a problem hiding this comment.
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.
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?
There was a problem hiding this comment.
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
endIf you want to override it in your subclass, just do:
class Project < ActiveRecord::Base
def self.implicit_order_column
"created_at"
end
endThat way we don't have to deal with ivar / cache management.
There was a problem hiding this comment.
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.
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.
Hmmm.. I guess you'd set it to nil, then || it in the caller
There was a problem hiding this comment.
@eileencodes / @tenderlove any thoughts on this? should I update to use class_attribute instead?
There was a problem hiding this comment.
@tekin yes, make this a class attribute and then we'll merge it. 😊
There was a problem hiding this comment.
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
|
🎉 |
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
firstorlastwithout 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 thatfirstandlastwill return more predictable results.Example:
This takes a different approach to #34236, which automatically switches to using
created_atif the primary key is a UUID and acreated_atcolumn exists.