Skip to content

Commit 1635152

Browse files
committed
#last and #first finders should use query_constraints for ordering
1 parent 350ef7e commit 1635152

File tree

3 files changed

+59
-8
lines changed

3 files changed

+59
-8
lines changed

activerecord/lib/active_record/persistence.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,13 +456,14 @@ def update!(id = :all, attributes)
456456
end
457457

458458
# Accepts a list of attribute names to be used in the WHERE clause
459-
# of SELECT / UPDATE / DELETE queries.
459+
# of SELECT / UPDATE / DELETE queries and in the ORDER BY clause for `#first` and `#last` finder methods.
460460
#
461461
# class Developer < ActiveRecord::Base
462462
# query_constraints :company_id, :id
463463
# end
464464
#
465465
# developer = Developer.first
466+
# SELECT "developers".* FROM "developers" ORDER BY "developers"."company_id" ASC, "developers"."id" ASC LIMIT 1
466467
# developer.inspect # => #<Developer id: 1, company_id: 1, ...>
467468
#
468469
# developer.update!(name: "Nikita")

activerecord/lib/active_record/relation/finder_methods.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -576,12 +576,12 @@ def find_last(limit)
576576
end
577577

578578
def ordered_relation
579-
if order_values.empty? && (implicit_order_column || primary_key)
580-
if implicit_order_column && primary_key && implicit_order_column != primary_key
581-
order(table[implicit_order_column].asc, table[primary_key].asc)
582-
else
583-
order(table[implicit_order_column || primary_key].asc)
584-
end
579+
if order_values.empty? && (implicit_order_column || !query_constraints_list.empty?)
580+
# use query_constraints_list as the order clause if there is no implicit_order_column
581+
# otherwise remove the implicit order column from the query constraints list if it's there
582+
# and prepend it to the beginning of the list
583+
order_columns = implicit_order_column.nil? ? query_constraints_list : ([implicit_order_column] | query_constraints_list)
584+
order(*order_columns.map { |column| table[column].asc })
585585
else
586586
self
587587
end

activerecord/test/cases/finder_test.rb

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@
2323
require "models/tyre"
2424
require "models/subscriber"
2525
require "models/non_primary_key"
26+
require "models/clothing_item"
2627
require "support/stubs/strong_parameters"
2728
require "support/async_helper"
2829

2930
class FinderTest < ActiveRecord::TestCase
3031
include AsyncHelper
3132

32-
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars
33+
fixtures :companies, :topics, :entrants, :developers, :developers_projects,
34+
:posts, :comments, :accounts, :authors, :author_addresses, :customers,
35+
:categories, :categorizations, :cars, :clothing_items
3336

3437
def test_find_by_id_with_hash
3538
assert_nothing_raised do
@@ -1033,6 +1036,33 @@ def test_implicit_order_for_model_without_primary_key
10331036
NonPrimaryKey.implicit_order_column = old_implicit_order_column
10341037
end
10351038

1039+
def test_implicit_order_column_reorders_query_constraints
1040+
c = ClothingItem.connection
1041+
ClothingItem.implicit_order_column = "color"
1042+
quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1043+
quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1044+
1045+
assert_sql(/ORDER BY #{quoted_color} ASC, #{quoted_type} ASC LIMIT/i) do
1046+
assert_kind_of ClothingItem, ClothingItem.first
1047+
end
1048+
ensure
1049+
ClothingItem.implicit_order_column = nil
1050+
end
1051+
1052+
def test_implicit_order_column_prepends_query_constraints
1053+
c = ClothingItem.connection
1054+
ClothingItem.implicit_order_column = "description"
1055+
quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1056+
quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1057+
quoted_descrption = Regexp.escape(c.quote_table_name("clothing_items.description"))
1058+
1059+
assert_sql(/ORDER BY #{quoted_descrption} ASC, #{quoted_type} ASC, #{quoted_color} ASC LIMIT/i) do
1060+
assert_kind_of ClothingItem, ClothingItem.first
1061+
end
1062+
ensure
1063+
ClothingItem.implicit_order_column = nil
1064+
end
1065+
10361066
def test_take_and_first_and_last_with_integer_should_return_an_array
10371067
assert_kind_of Array, Topic.take(5)
10381068
assert_kind_of Array, Topic.first(5)
@@ -1731,6 +1761,26 @@ def test_finder_with_offset_string
17311761
end
17321762
end
17331763

1764+
test "#last for a model with composite query constraints" do
1765+
c = ClothingItem.connection
1766+
quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1767+
quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1768+
1769+
assert_sql(/ORDER BY #{quoted_type} DESC, #{quoted_color} DESC LIMIT/i) do
1770+
assert_kind_of ClothingItem, ClothingItem.last
1771+
end
1772+
end
1773+
1774+
test "#first for a model with composite query constraints" do
1775+
c = ClothingItem.connection
1776+
quoted_type = Regexp.escape(c.quote_table_name("clothing_items.clothing_type"))
1777+
quoted_color = Regexp.escape(c.quote_table_name("clothing_items.color"))
1778+
1779+
assert_sql(/ORDER BY #{quoted_type} ASC, #{quoted_color} ASC LIMIT/i) do
1780+
assert_kind_of ClothingItem, ClothingItem.first
1781+
end
1782+
end
1783+
17341784
private
17351785
def table_with_custom_primary_key
17361786
yield(Class.new(Toy) do

0 commit comments

Comments
 (0)