Consistency between first() and last() with limit #27597
Conversation
r? @pixeltrix (@rails-bot has picked a reviewer for you, use r? to override) |
Can you rebase? |
@kamipo Rebased to latest |
|
||
if limit_value.present? | ||
limit = [limit_value - index, limit].min | ||
limit = [limit, 0].max |
kamipo
Jan 8, 2018
Member
If limit <= 0
, we don't need a query to database.
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index e61cacf6a7..50d0f14b98 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -532,7 +532,11 @@ def find_nth_with_limit(index, limit)
else
relation = ordered_relation
- if limit_value.nil? || index < limit_value
+ if limit_value
+ limit = [limit_value - index, limit].min
+ end
+
+ if limit > 0
relation = relation.offset(offset_index + index) unless index.zero?
relation.limit(limit).to_a
else
If limit <= 0
, we don't need a query to database.
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index e61cacf6a7..50d0f14b98 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -532,7 +532,11 @@ def find_nth_with_limit(index, limit)
else
relation = ordered_relation
- if limit_value.nil? || index < limit_value
+ if limit_value
+ limit = [limit_value - index, limit].min
+ end
+
+ if limit > 0
relation = relation.offset(offset_index + index) unless index.zero?
relation.limit(limit).to_a
else
brchristian
Jan 8, 2018
Author
Contributor
Great point! I have updated the PR.
Great point! I have updated the PR.
@@ -693,6 +693,11 @@ def test_take_and_first_and_last_with_integer_should_return_an_array | |||
assert_kind_of Array, Topic.last(5) | |||
end | |||
|
|||
def test_take_and_first_and_last_with_limit_should_return_same_size | |||
topics = Topic.limit(1) | |||
assert_equal topics.first(2).size, topics.last(2).size |
kamipo
Jan 8, 2018
Member
first
with limit
doesn't work as expected, and inconsistent with last
is true.
But it is also inconsistent with loaded first
with limit
.
Can you more exercise tests like test_last_on_relation_with_limit_and_offset
?
def test_find_on_relation_with_limit_and_offset
post = posts("sti_comments")
comments = post.comments.order(id: :asc)
assert_equal comments.limit(2).to_a.first, comments.limit(2).first
assert_equal comments.limit(2).to_a.first(2), comments.limit(2).first(2)
assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3)
assert_equal comments.offset(2).to_a.first, comments.offset(2).first
assert_equal comments.offset(2).to_a.first(2), comments.offset(2).first(2)
assert_equal comments.offset(2).to_a.first(3), comments.offset(2).first(3)
comments = comments.offset(1)
assert_equal comments.limit(2).to_a.first, comments.limit(2).first
assert_equal comments.limit(2).to_a.first(2), comments.limit(2).first(2)
assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3)
end
rails/activerecord/test/cases/finder_test.rb
Lines 672 to 688
in
7c69351
first
with limit
doesn't work as expected, and inconsistent with last
is true.
But it is also inconsistent with loaded first
with limit
.
Can you more exercise tests like test_last_on_relation_with_limit_and_offset
?
def test_find_on_relation_with_limit_and_offset
post = posts("sti_comments")
comments = post.comments.order(id: :asc)
assert_equal comments.limit(2).to_a.first, comments.limit(2).first
assert_equal comments.limit(2).to_a.first(2), comments.limit(2).first(2)
assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3)
assert_equal comments.offset(2).to_a.first, comments.offset(2).first
assert_equal comments.offset(2).to_a.first(2), comments.offset(2).first(2)
assert_equal comments.offset(2).to_a.first(3), comments.offset(2).first(3)
comments = comments.offset(1)
assert_equal comments.limit(2).to_a.first, comments.limit(2).first
assert_equal comments.limit(2).to_a.first(2), comments.limit(2).first(2)
assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3)
end
rails/activerecord/test/cases/finder_test.rb
Lines 672 to 688 in 7c69351
brchristian
Jan 8, 2018
Author
Contributor
No problem, I have added additional tests for compatibility here between limit(x).first(y)
and limit(x).to_a.first(y)
, and they all now pass as well.
No problem, I have added additional tests for compatibility here between limit(x).first(y)
and limit(x).to_a.first(y)
, and they all now pass as well.
@kamipo Thanks for code review. I have refactored and added the additional tests. |
|
||
assert_equal topics.limit(1).to_a.first(2).size, topics.limit(1).first(2).size | ||
assert_equal topics.limit(2).to_a.first(2).size, topics.limit(2).first(2).size | ||
assert_equal topics.limit(3).to_a.first(2).size, topics.limit(3).first(2).size |
kamipo
Jan 9, 2018
Member
Can you remove .size
? These need to be the same result regardless of whether they are loaded or not.
Can you remove .size
? These need to be the same result regardless of whether they are loaded or not.
|
||
assert_equal topics.limit(1).to_a.last(2).size, topics.limit(1).last(2).size | ||
assert_equal topics.limit(2).to_a.last(2).size, topics.limit(2).last(2).size | ||
assert_equal topics.limit(3).to_a.last(2).size, topics.limit(3).last(2).size |
kamipo
Jan 9, 2018
Member
These assersions have already been covered by test_last_on_relation_with_limit_and_offset
.
rails/activerecord/test/cases/finder_test.rb
Lines 672 to 688
in
7c69351
These assersions have already been covered by test_last_on_relation_with_limit_and_offset
.
rails/activerecord/test/cases/finder_test.rb
Lines 672 to 688 in 7c69351
@@ -1,3 +1,11 @@ | |||
* Fixed inconsistency between `first(n)` and `last(n)` when | |||
used with `limit()` The `first` finder respects the `limit()`, | |||
making it consistent with `last(n)`. |
kamipo
Jan 9, 2018
Member
I think that we should note fixing inconsistency first(n)
with either loaded or not.
Previously, we need to use relation.to_a.first(n)
as a workaround, but now, we can use relation.first(n)
simply.
I think that we should note fixing inconsistency first(n)
with either loaded or not.
Previously, we need to use relation.to_a.first(n)
as a workaround, but now, we can use relation.first(n)
simply.
|
||
assert_equal topics.limit(1).to_a.first(2).size, topics.limit(1).to_a.last(2).size | ||
assert_equal topics.limit(2).to_a.first(2).size, topics.limit(2).to_a.last(2).size | ||
assert_equal topics.limit(3).to_a.first(2).size, topics.limit(3).to_a.last(2).size |
kamipo
Jan 9, 2018
Member
Array#first
and Array#last
are in the Ruby Core. These assertions are not necessary.
http://ruby-doc.org/core-2.5.0/Array.html#method-i-first
http://ruby-doc.org/core-2.5.0/Array.html#method-i-last
Array#first
and Array#last
are in the Ruby Core. These assertions are not necessary.
http://ruby-doc.org/core-2.5.0/Array.html#method-i-first
http://ruby-doc.org/core-2.5.0/Array.html#method-i-last
|
||
assert_equal topics.limit(1).first(2).size, topics.limit(1).last(2).size | ||
assert_equal topics.limit(2).first(2).size, topics.limit(2).last(2).size | ||
assert_equal topics.limit(3).first(2).size, topics.limit(3).last(2).size |
kamipo
Jan 9, 2018
Member
Previously relation.to_a.first(n)
, relation.to_a.last(n)
, and relation.last(n)
works expected, but only relation.first(n)
does not.
Consistency between relation.to_a.last(n)
and relation.last(n)
have already been covered by test_last_on_relation_with_limit_and_offset
.
Whether relation.first(n)
works correctly should compare relation.to_a.first(n)
rather than relation.last(n)
.
Previously relation.to_a.first(n)
, relation.to_a.last(n)
, and relation.last(n)
works expected, but only relation.first(n)
does not.
Consistency between relation.to_a.last(n)
and relation.last(n)
have already been covered by test_last_on_relation_with_limit_and_offset
.
Whether relation.first(n)
works correctly should compare relation.to_a.first(n)
rather than relation.last(n)
.
@kamipo Great points about the tests and CHANGELOG. I agree with you and I have made all the changes you suggested. |
Looks good to me |
@kamipo Squashed |
(PS, Special thanks to @mmlindeboom for helpful discussion!) |
Fixes #23979.
As discussed in #23979, there was an inconsistency between the way that
first()
andlast()
would interact withlimit
. Specifically:This PR is a refactor and rebase of #24124, with a simpler test suite and simpler implementation.
Discussion with Rails community members as well as DHH in #23598 (comment) showed that the behavior or
first
should be brought into line withlast
(rather than vice-versa).This PR resolves the inconsistency between
first
andlast
when used in conjunction withlimit
.