Skip to content

Commit 3b9982a

Browse files
committed
Make implicit order column configurable
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
1 parent 85b0803 commit 3b9982a

File tree

4 files changed

+45
-2
lines changed

4 files changed

+45
-2
lines changed

activerecord/CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
* Make the implicit order column configurable.
2+
3+
When calling ordered finder methods such as +first+ or +last+ without an
4+
explicit order clause, ActiveRecord sorts records by primary key. This can
5+
result in unpredictable and surprising behaviour when the primary key is
6+
not an auto-incrementing integer, for example when it's a UUID. This change
7+
makes it possible to override the column used for implicit ordering such
8+
that +first+ and +last+ will return more predictable results.
9+
10+
Example:
11+
12+
class Project < ActiveRecord::Base
13+
self.implicit_order_column = "created_at"
14+
end
15+
16+
*Tekin Suleyman*
17+
118
* Bump minimum PostgreSQL version to 9.3.
219

320
*Yasuo Honda*

activerecord/lib/active_record/model_schema.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,21 @@ module ModelSchema
102102
# If true, the default table name for a Product class will be "products". If false, it would just be "product".
103103
# See table_name for the full rules on table/class naming. This is true, by default.
104104

105+
##
106+
# :singleton-method: implicit_order_column
107+
# :call-seq: implicit_order_column
108+
#
109+
# The name of the column records are ordered by if no explicit order clause
110+
# is used during an ordered finder call. If not set the primary key is used.
111+
112+
##
113+
# :singleton-method: implicit_order_column=
114+
# :call-seq: implicit_order_column=(column_name)
115+
#
116+
# Sets the column to sort records by when no explicit order clause is used
117+
# during an ordered finder call. Useful when the primary key is not an
118+
# auto-incrementing integer, for example when it's a UUID. Note that using
119+
# a non-unique column can result in non-deterministic results.
105120
included do
106121
mattr_accessor :primary_key_prefix_type, instance_writer: false
107122

@@ -110,6 +125,7 @@ module ModelSchema
110125
class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations"
111126
class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata"
112127
class_attribute :pluralize_table_names, instance_writer: false, default: true
128+
class_attribute :implicit_order_column, instance_accessor: false
113129

114130
self.protected_environments = ["production"]
115131
self.inheritance_column = "type"

activerecord/lib/active_record/relation/finder_methods.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,8 +550,8 @@ def find_last(limit)
550550
end
551551

552552
def ordered_relation
553-
if order_values.empty? && primary_key
554-
order(arel_attribute(primary_key).asc)
553+
if order_values.empty? && (implicit_order_column || primary_key)
554+
order(arel_attribute(implicit_order_column || primary_key).asc)
555555
else
556556
self
557557
end

activerecord/test/cases/finder_test.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,16 @@ def test_first_have_determined_order_by_default
741741
assert_equal expected, clients.limit(5).first(2)
742742
end
743743

744+
def test_implicit_order_column_is_configurable
745+
old_implicit_order_column = Topic.implicit_order_column
746+
Topic.implicit_order_column = "title"
747+
748+
assert_equal topics(:fifth), Topic.first
749+
assert_equal topics(:third), Topic.last
750+
ensure
751+
Topic.implicit_order_column = old_implicit_order_column
752+
end
753+
744754
def test_take_and_first_and_last_with_integer_should_return_an_array
745755
assert_kind_of Array, Topic.take(5)
746756
assert_kind_of Array, Topic.first(5)

0 commit comments

Comments
 (0)