1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge pull request #16400 from bogdan/last-with-sql

Always use SQL limit in Relation#last when limit argument given
This commit is contained in:
Rafael França 2016-01-28 13:53:32 -05:00
commit 9f3730a516
3 changed files with 60 additions and 31 deletions

View file

@ -1,3 +1,23 @@
* Rework `ActiveRecord::Relation#last`
1. Always find last with ruby if relation is loaded
2. Always use SQL instead if relation is not loaded.
3. Deprecated relation loading when SQL order can not be automatically reversed
Topic.order("title").load.last(3)
# before: SELECT ...
# after: No SQL
Topic.order("title").last
# before: SELECT * FROM `topics`
# after: SELECT * FROM `topics` ORDER BY `topics`.`title` DESC LIMIT 1
Topic.order("coalesce(author, title)").last
# before: SELECT * FROM `topics`
# after: Deprecation Warning for irreversible order
*Bogdan Gusiev*
* `ActiveRecord::Relation#reverse_order` throws `ActiveRecord::IrreversibleOrderError` * `ActiveRecord::Relation#reverse_order` throws `ActiveRecord::IrreversibleOrderError`
when the order can not be reversed using current trivial algorithm. when the order can not be reversed using current trivial algorithm.
Also raises the same error when `#reverse_order` is called on Also raises the same error when `#reverse_order` is called on

View file

@ -145,15 +145,23 @@ module ActiveRecord
# #
# [#<Person id:4>, #<Person id:3>, #<Person id:2>] # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
def last(limit = nil) def last(limit = nil)
if limit return find_last(limit) if loaded?
if order_values.empty? && primary_key
order(arel_table[primary_key].desc).limit(limit).reverse result = order_values.empty? && primary_key ? order(arel_table[primary_key].desc) : reverse_order
else result = result.limit!(limit || 1)
to_a.last(limit) limit ? result.reverse : result.first
end rescue ActiveRecord::IrreversibleOrderError
else ActiveSupport::Deprecation.warn(<<-WARNING.squish)
find_last Finding a last element by loading the relation when SQL ORDER
can not be reversed is deprecated.
Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
Please call `to_a.last` if you still want to load the relation.
WARNING
find_last(limit)
end end
def find_last(limit)
limit ? to_a.last(limit) : to_a.last
end end
# Same as #last but raises ActiveRecord::RecordNotFound if no record # Same as #last but raises ActiveRecord::RecordNotFound if no record
@ -523,19 +531,6 @@ module ActiveRecord
relation.limit(limit).to_a relation.limit(limit).to_a
end end
def find_last
if loaded?
@records.last
else
@last ||=
if limit_value
to_a.last
else
reverse_order.limit(1).to_a.first
end
end
end
private private
def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc: def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:

View file

@ -506,7 +506,7 @@ class FinderTest < ActiveRecord::TestCase
end end
end end
def test_take_and_first_and_last_with_integer_should_use_sql_limit def test_take_and_first_and_last_with_integer_should_use_sql
assert_sql(/LIMIT|ROWNUM <=/) { Topic.take(3).entries } assert_sql(/LIMIT|ROWNUM <=/) { Topic.take(3).entries }
assert_sql(/LIMIT|ROWNUM <=/) { Topic.first(2).entries } assert_sql(/LIMIT|ROWNUM <=/) { Topic.first(2).entries }
assert_sql(/LIMIT|ROWNUM <=/) { Topic.last(5).entries } assert_sql(/LIMIT|ROWNUM <=/) { Topic.last(5).entries }
@ -516,16 +516,30 @@ class FinderTest < ActiveRecord::TestCase
assert_equal Topic.order("title").to_a.last(2), Topic.order("title").last(2) assert_equal Topic.order("title").to_a.last(2), Topic.order("title").last(2)
end end
def test_last_with_integer_and_order_should_not_use_sql_limit def test_last_with_integer_and_order_should_use_sql
query = assert_sql { Topic.order("title").last(5).entries } relation = Topic.order("title")
assert_equal 1, query.length assert_queries(1) { relation.last(5) }
assert_no_match(/LIMIT/, query.first) assert !relation.loaded?
end end
def test_last_with_integer_and_reorder_should_not_use_sql_limit def test_last_with_integer_and_reorder_should_use_sql
query = assert_sql { Topic.reorder("title").last(5).entries } relation = Topic.reorder("title")
assert_equal 1, query.length assert_queries(1) { relation.last(5) }
assert_no_match(/LIMIT/, query.first) assert !relation.loaded?
end
def test_last_on_loaded_relation_should_not_use_sql
relation = Topic.limit(10).load
assert_no_queries do
relation.last
relation.last(2)
end
end
def test_last_with_irreversible_order
assert_deprecated do
Topic.order("coalesce(author_name, title)").last
end
end end
def test_take_and_first_and_last_with_integer_should_return_an_array def test_take_and_first_and_last_with_integer_should_return_an_array