From 16a476e4f8f802774ae7c8dca2e59f4e672dc591 Mon Sep 17 00:00:00 2001
From: Ben Woosley <ben.woosley@gmail.com>
Date: Fri, 23 Oct 2015 14:59:31 -0500
Subject: Deprecate passing `offset` to `find_nth`

All uses of the `offset` are passing `offset_index`. Better to push
down the `offset` consideration into `find_nth`.

This also works toward enabling `find_nth_with_limit` to take
advantage of the `loaded?` state of the relation.
---
 activerecord/CHANGELOG.md                          |  5 +++++
 .../lib/active_record/relation/finder_methods.rb   | 24 ++++++++++++++--------
 2 files changed, 21 insertions(+), 8 deletions(-)

(limited to 'activerecord')

diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 2551841aaf..5daec6fb34 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,8 @@
+*   Deprecate sending the `offset` argument to `find_nth`. Please use the
+    `offset` method on relation instead.
+
+    *Ben Woosley*
+
 ## Rails 5.0.0.beta1 (December 18, 2015) ##
 
 *   Order the result of `find(ids)` to match the passed array, if the relation
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 19244bcf95..4a0867289e 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -119,7 +119,7 @@ module ActiveRecord
       if limit
         find_nth_with_limit(offset_index, limit)
       else
-        find_nth(0, offset_index)
+        find_nth 0
       end
     end
 
@@ -169,7 +169,7 @@ module ActiveRecord
     #   Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
     #   Person.where(["user_name = :u", { u: user_name }]).second
     def second
-      find_nth(1, offset_index)
+      find_nth 1
     end
 
     # Same as #second but raises ActiveRecord::RecordNotFound if no record
@@ -185,7 +185,7 @@ module ActiveRecord
     #   Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
     #   Person.where(["user_name = :u", { u: user_name }]).third
     def third
-      find_nth(2, offset_index)
+      find_nth 2
     end
 
     # Same as #third but raises ActiveRecord::RecordNotFound if no record
@@ -201,7 +201,7 @@ module ActiveRecord
     #   Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
     #   Person.where(["user_name = :u", { u: user_name }]).fourth
     def fourth
-      find_nth(3, offset_index)
+      find_nth 3
     end
 
     # Same as #fourth but raises ActiveRecord::RecordNotFound if no record
@@ -217,7 +217,7 @@ module ActiveRecord
     #   Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
     #   Person.where(["user_name = :u", { u: user_name }]).fifth
     def fifth
-      find_nth(4, offset_index)
+      find_nth 4
     end
 
     # Same as #fifth but raises ActiveRecord::RecordNotFound if no record
@@ -233,7 +233,7 @@ module ActiveRecord
     #   Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
     #   Person.where(["user_name = :u", { u: user_name }]).forty_two
     def forty_two
-      find_nth(41, offset_index)
+      find_nth 41
     end
 
     # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
@@ -488,17 +488,25 @@ module ActiveRecord
       end
     end
 
-    def find_nth(index, offset)
+    def find_nth(index, offset = nil)
       if loaded?
         @records[index]
       else
+        if offset
+          ActiveSupport::Deprecation.warn(<<-MSG.squish)
+            Passing an offset argument to find_nth is deprecated,
+            please use Relation#offset instead.
+          MSG
+        else
+          offset = offset_index
+        end
         offset += index
         @offsets[offset] ||= find_nth_with_limit(offset, 1).first
       end
     end
 
     def find_nth!(index)
-      find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+      find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
     end
 
     def find_nth_with_limit(offset, limit)
-- 
cgit v1.2.3


From b42c3255bf22e54f459751d5370e8befc33e84ea Mon Sep 17 00:00:00 2001
From: Ben Woosley <ben.woosley@gmail.com>
Date: Fri, 23 Oct 2015 16:13:36 -0500
Subject: Fix `first(limit)` to take advantage of `loaded?` records if
 available

I realized that `first(2)`, etc. was unnecessarily querying for the
records when they were already preloaded. This was because
`find_nth_with_limit` can not know which `@records` to return because
it conflates the `offset` and `index` into a single variable, while
the `@records` only needs the `index` itself to select the proper
record.

Because `find_nth` and `find_nth_with_limit` are public methods, I
instead introduced a private method `find_nth_with_limit_and_offset`
which is called internally and handles the `loaded?` checking.

Once the `offset` argument is removed from `find_nth`,
`find_nth_with_limit_and_offset` can be collapsed into
`find_nth_with_limit`, with `offset` always equal to `offset_index`.
---
 activerecord/CHANGELOG.md                          |  5 +++++
 .../lib/active_record/relation/finder_methods.rb   | 25 +++++++++++++++++-----
 activerecord/test/cases/relations_test.rb          | 17 ++++++++++++---
 3 files changed, 39 insertions(+), 8 deletions(-)

(limited to 'activerecord')

diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 5daec6fb34..9144ab6695 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,8 @@
+*   When calling `first` with a `limit` argument, return directly from the
+    `loaded?` records if available.
+
+    *Ben Woosley*
+
 *   Deprecate sending the `offset` argument to `find_nth`. Please use the
     `offset` method on relation instead.
 
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 4a0867289e..3cbb12a09d 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -117,7 +117,7 @@ module ActiveRecord
     #
     def first(limit = nil)
       if limit
-        find_nth_with_limit(offset_index, limit)
+        find_nth_with_limit_and_offset(0, limit, offset: offset_index)
       else
         find_nth 0
       end
@@ -492,6 +492,9 @@ module ActiveRecord
       if loaded?
         @records[index]
       else
+        # TODO: once the offset argument is removed we rely on offset_index
+        # within find_nth_with_limit, rather than pass it in via
+        # find_nth_with_limit_and_offset
         if offset
           ActiveSupport::Deprecation.warn(<<-MSG.squish)
             Passing an offset argument to find_nth is deprecated,
@@ -500,8 +503,7 @@ module ActiveRecord
         else
           offset = offset_index
         end
-        offset += index
-        @offsets[offset] ||= find_nth_with_limit(offset, 1).first
+        @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
       end
     end
 
@@ -509,14 +511,16 @@ module ActiveRecord
       find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
     end
 
-    def find_nth_with_limit(offset, limit)
+    def find_nth_with_limit(index, limit)
+      # TODO: once the offset argument is removed from find_nth,
+      # find_nth_with_limit_and_offset can be merged into this method
       relation = if order_values.empty? && primary_key
                    order(arel_table[primary_key].asc)
                  else
                    self
                  end
 
-      relation = relation.offset(offset) unless offset.zero?
+      relation = relation.offset(index) unless index.zero?
       relation.limit(limit).to_a
     end
 
@@ -532,5 +536,16 @@ module ActiveRecord
           end
       end
     end
+
+    private
+
+    def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
+      if loaded?
+        @records[index, limit]
+      else
+        index += offset
+        find_nth_with_limit(index, limit)
+      end
+    end
   end
 end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 7149c7d072..df0c2f1f92 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -111,10 +111,21 @@ class RelationTest < ActiveRecord::TestCase
 
   def test_loaded_first
     topics = Topic.all.order('id ASC')
+    topics.to_a # force load
 
-    assert_queries(1) do
-      topics.to_a # force load
-      2.times { assert_equal "The First Topic", topics.first.title }
+    assert_no_queries do
+      assert_equal "The First Topic", topics.first.title
+    end
+
+    assert topics.loaded?
+  end
+
+  def test_loaded_first_with_limit
+    topics = Topic.all.order('id ASC')
+    topics.to_a # force load
+
+    assert_no_queries do
+      assert_equal "The First Topic", topics.first(2).first.title
     end
 
     assert topics.loaded?
-- 
cgit v1.2.3