Permalink
Browse files

Ensure AR #second, #third, etc. finders work through associations

This commit fixes two regressions introduced in cafe31a where
newly created finder methods #second, #third, #forth, and #fifth
caused a NoMethodError error on reload associations and where we
were pulling the wrong element out of cached associations.

Examples:

  some_book.authors.reload.second

  # Before
  # => NoMethodError: undefined method 'first' for nil:NilClass

  # After
  # => #<Author id: 2, name: "Sally Second", ...>

  some_book.first.authors.first
  some_book.first.authors.second

  # Before
  # => #<Author id: 1, name: "Freddy First", ...>
  # => #<Author id: 1, name: "Freddy First", ...>

  # After
  # => #<Author id: 1, name: "Freddy First", ...>
  # => #<Author id: 2, name: "Sally Second", ...>

Fixes #13783.
  • Loading branch information...
1 parent 9383de4 commit 03855e790de2224519f55382e3c32118be31eeff @terracatta terracatta committed Jan 21, 2014
@@ -96,11 +96,31 @@ def find(*args)
end
def first(*args)
- first_or_last(:first, *args)
+ first_nth_or_last(:first, *args)
+ end
+
+ def second(*args)
+ first_nth_or_last(:second, *args)
+ end
+
+ def third(*args)
+ first_nth_or_last(:third, *args)
+ end
+
+ def fourth(*args)
+ first_nth_or_last(:fourth, *args)
+ end
+
+ def fifth(*args)
+ first_nth_or_last(:fifth, *args)
+ end
+
+ def forty_two(*args)
+ first_nth_or_last(:forty_two, *args)
end
def last(*args)
- first_or_last(:last, *args)
+ first_nth_or_last(:last, *args)
end
def build(attributes = {}, &block)
@@ -526,7 +546,7 @@ def callbacks_for(callback_name)
# * target already loaded
# * owner is new record
# * target contains new or changed record(s)
- def fetch_first_or_last_using_find?(args)
+ def fetch_first_nth_or_last_using_find?(args)
if args.first.is_a?(Hash)
true
else
@@ -564,10 +584,10 @@ def find_by_scan(*args)
end
# Fetches the first/last using SQL if possible, otherwise from the target array.
- def first_or_last(type, *args)
+ def first_nth_or_last(type, *args)
args.shift if args.first.is_a?(Hash) && args.first.empty?
- collection = fetch_first_or_last_using_find?(args) ? scope : load_target
+ collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
collection.send(type, *args).tap do |record|
set_inverse_instance record if record.is_a? ActiveRecord::Base
end
@@ -170,6 +170,32 @@ def first(*args)
@association.first(*args)
end
+ # Same as +first+ except returns only the second record.
+ def second(*args)
+ @association.second(*args)
+ end
+
+ # Same as +first+ except returns only the third record.
+ def third(*args)
+ @association.third(*args)
+ end
+
+ # Same as +first+ except returns only the fourth record.
+ def fourth(*args)
+ @association.fourth(*args)
+ end
+
+ # Same as +first+ except returns only the fifth record.
+ def fifth(*args)
+ @association.fifth(*args)
+ end
+
+ # Same as +first+ except returns only the forty second record.
+ # Also known as accessing "the reddit".
+ def forty_two(*args)
+ @association.forty_two(*args)
+ end
+
# Returns the last record, or the last +n+ records, from the collection.
# If the collection is empty, the first form returns +nil+, and the second
# form returns an empty array.
@@ -129,7 +129,7 @@ def first(limit = nil)
if limit
find_nth_with_limit(offset_value, limit)
else
- find_nth(offset_value)
+ find_nth(:first, offset_value)
end
end
@@ -179,7 +179,7 @@ def last!
# 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(offset_value ? offset_value + 1 : 1)
+ find_nth(:second, offset_value ? offset_value + 1 : 1)
end
# Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -195,7 +195,7 @@ def second!
# 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(offset_value ? offset_value + 2 : 2)
+ find_nth(:third, offset_value ? offset_value + 2 : 2)
end
# Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -211,7 +211,7 @@ def third!
# 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(offset_value ? offset_value + 3 : 3)
+ find_nth(:fourth, offset_value ? offset_value + 3 : 3)
end
# Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -227,7 +227,7 @@ def fourth!
# 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(offset_value ? offset_value + 4 : 4)
+ find_nth(:fifth, offset_value ? offset_value + 4 : 4)
end
# Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -243,7 +243,7 @@ def fifth!
# Person.offset(3).forty_two # returns the fifth object from OFFSET 3 (which is OFFSET 44)
# Person.where(["user_name = :u", { u: user_name }]).forty_two
def forty_two
- find_nth(offset_value ? offset_value + 41 : 41)
+ find_nth(:forty_two, offset_value ? offset_value + 41 : 41)
end
# Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -444,9 +444,9 @@ def find_take
end
end
- def find_nth(offset)
+ def find_nth(ordinal, offset)
if loaded?
- @records.first
+ @records.send(ordinal)
else
@offsets[offset] ||= find_nth_with_limit(offset, 1).first
end
Oops, something went wrong.

0 comments on commit 03855e7

Please sign in to comment.