aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/relation/finder_methods.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/relation/finder_methods.rb')
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb198
1 files changed, 165 insertions, 33 deletions
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 4984dbd277..088bc203b7 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -1,3 +1,6 @@
+require 'active_support/deprecation'
+require 'active_support/core_ext/string/filters'
+
module ActiveRecord
module FinderMethods
ONE_AS_ONE = '1 AS one'
@@ -63,7 +66,7 @@ module ActiveRecord
# # returns an Array of the required fields, available since Rails 3.1.
def find(*args)
if block_given?
- to_a.find { |*block_args| yield(*block_args) }
+ to_a.find(*args) { |*block_args| yield(*block_args) }
else
find_with_ids(*args)
end
@@ -79,12 +82,16 @@ module ActiveRecord
# Post.find_by "published_at < ?", 2.weeks.ago
def find_by(*args)
where(*args).take
+ rescue RangeError
+ nil
end
# Like <tt>find_by</tt>, except that if no record is found, raises
# an <tt>ActiveRecord::RecordNotFound</tt> error.
def find_by!(*args)
where(*args).take!
+ rescue RangeError
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range value"
end
# Gives a record (or N records if a parameter is supplied) without any implied
@@ -101,7 +108,7 @@ module ActiveRecord
# Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found. Note that <tt>take!</tt> accepts no arguments.
def take!
- take or raise RecordNotFound
+ take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
# Find the first record (or first N records if a parameter is supplied).
@@ -127,16 +134,16 @@ module ActiveRecord
#
def first(limit = nil)
if limit
- find_first_with_limit(limit)
+ find_nth_with_limit(offset_index, limit)
else
- find_first
+ find_nth(0, offset_index)
end
end
# Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found. Note that <tt>first!</tt> accepts no arguments.
def first!
- first or raise RecordNotFound
+ find_nth! 0
end
# Find the last record (or last N records if a parameter is supplied).
@@ -169,7 +176,87 @@ module ActiveRecord
# Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found. Note that <tt>last!</tt> accepts no arguments.
def last!
- last or raise RecordNotFound
+ last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ end
+
+ # Find the second record.
+ # If no order is defined it will order by primary key.
+ #
+ # Person.second # returns the second object fetched by SELECT * FROM people
+ # 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)
+ end
+
+ # Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found.
+ def second!
+ find_nth! 1
+ end
+
+ # Find the third record.
+ # If no order is defined it will order by primary key.
+ #
+ # Person.third # returns the third object fetched by SELECT * FROM people
+ # 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)
+ end
+
+ # Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found.
+ def third!
+ find_nth! 2
+ end
+
+ # Find the fourth record.
+ # If no order is defined it will order by primary key.
+ #
+ # Person.fourth # returns the fourth object fetched by SELECT * FROM people
+ # 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)
+ end
+
+ # Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found.
+ def fourth!
+ find_nth! 3
+ end
+
+ # Find the fifth record.
+ # If no order is defined it will order by primary key.
+ #
+ # Person.fifth # returns the fifth object fetched by SELECT * FROM people
+ # 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)
+ end
+
+ # Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found.
+ def fifth!
+ find_nth! 4
+ end
+
+ # Find the forty-second record. Also known as accessing "the reddit".
+ # If no order is defined it will order by primary key.
+ #
+ # Person.forty_two # returns the forty-second object fetched by SELECT * FROM people
+ # 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)
+ end
+
+ # Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found.
+ def forty_two!
+ find_nth! 41
end
# Returns +true+ if a record exists in the table that matches the +id+ or
@@ -200,7 +287,14 @@ module ActiveRecord
# Person.exists?(false)
# Person.exists?
def exists?(conditions = :none)
- conditions = conditions.id if Base === conditions
+ if Base === conditions
+ conditions = conditions.id
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ You are passing an instance of ActiveRecord::Base to `exists?`.
+ Please pass the id of the object by calling `.id`
+ MSG
+ end
+
return false if !conditions
relation = apply_join_dependency(self, construct_join_dependency)
@@ -212,10 +306,12 @@ module ActiveRecord
when Array, Hash
relation = relation.where(conditions)
else
- relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
+ unless conditions == :none
+ relation = where(primary_key => conditions)
+ end
end
- connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
+ connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false
end
# This method is called whenever no records are found with either a single
@@ -227,13 +323,13 @@ module ActiveRecord
# the expected number of results should be provided in the +expected_size+
# argument.
def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
- conditions = arel.where_sql
+ conditions = arel.where_sql(@klass.arel_engine)
conditions = " [#{conditions}]" if conditions
if Array(ids).size == 1
- error = "Couldn't find #{@klass.name} with #{primary_key}=#{ids}#{conditions}"
+ error = "Couldn't find #{@klass.name} with '#{primary_key}'=#{ids}#{conditions}"
else
- error = "Couldn't find all #{@klass.name.pluralize} with IDs "
+ error = "Couldn't find all #{@klass.name.pluralize} with '#{primary_key}': "
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
end
@@ -242,8 +338,21 @@ module ActiveRecord
private
+ def offset_index
+ offset_value || 0
+ end
+
def find_with_associations
- join_dependency = construct_join_dependency
+ # NOTE: the JoinDependency constructed here needs to know about
+ # any joins already present in `self`, so pass them in
+ #
+ # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136
+ # incorrect SQL is generated. In that case, the join dependency for
+ # SpecialCategorizations is constructed without knowledge of the
+ # preexisting join in joins_values to categorizations (by way of
+ # the `has_many :through` for categories).
+ #
+ join_dependency = construct_join_dependency(joins_values)
aliases = join_dependency.aliases
relation = select aliases.columns
@@ -255,7 +364,8 @@ module ActiveRecord
if ActiveRecord::NullRelation === relation
[]
else
- rows = connection.select_all(relation.arel, 'SQL', relation.bind_values.dup)
+ arel = relation.arel
+ rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
join_dependency.instantiate(rows, aliases)
end
end
@@ -267,7 +377,15 @@ module ActiveRecord
end
def construct_relation_for_association_calculations
- apply_join_dependency(self, construct_join_dependency(arel.froms.first))
+ from = arel.froms.first
+ if Arel::Table === from
+ apply_join_dependency(self, construct_join_dependency)
+ else
+ # FIXME: as far as I can tell, `from` will always be an Arel::Table.
+ # There are no tests that test this branch, but presumably it's
+ # possible for `from` to be a list?
+ apply_join_dependency(self, construct_join_dependency(from))
+ end
end
def apply_join_dependency(relation, join_dependency)
@@ -279,7 +397,7 @@ module ActiveRecord
else
if relation.limit_value
limited_ids = limited_ids_for(relation)
- limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
+ limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
end
relation.except(:limit, :offset)
end
@@ -290,13 +408,14 @@ module ActiveRecord
"#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
relation = relation.except(:select).select(values).distinct!
+ arel = relation.arel
- id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
+ id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
id_rows.map {|row| row[primary_key]}
end
def using_limitable_reflections?(reflections)
- reflections.none? { |r| r.collection? }
+ reflections.none?(&:collection?)
end
protected
@@ -318,15 +437,20 @@ module ActiveRecord
else
find_some(ids)
end
+ rescue RangeError
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
end
def find_one(id)
- id = id.id if ActiveRecord::Base === id
+ if ActiveRecord::Base === id
+ id = id.id
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ You are passing an instance of ActiveRecord::Base to `find`.
+ Please pass the id of the object by calling `.id`
+ MSG
+ end
- column = columns_hash[primary_key]
- substitute = connection.substitute_at(column, bind_values.length)
- relation = where(table[primary_key].eq(substitute))
- relation.bind_values += [[column, id]]
+ relation = where(primary_key => id)
record = relation.take
raise_record_not_found_exception!(id, 0, 1) unless record
@@ -335,7 +459,7 @@ module ActiveRecord
end
def find_some(ids)
- result = where(table[primary_key].in(ids)).to_a
+ result = where(primary_key => ids).to_a
expected_size =
if limit_value && ids.size > limit_value
@@ -364,20 +488,28 @@ module ActiveRecord
end
end
- def find_first
+ def find_nth(index, offset)
if loaded?
- @records.first
+ @records[index]
else
- @first ||= find_first_with_limit(1).first
+ offset += index
+ @offsets[offset] ||= find_nth_with_limit(offset, 1).first
end
end
- def find_first_with_limit(limit)
- if order_values.empty? && primary_key
- order(arel_table[primary_key].asc).limit(limit).to_a
- else
- limit(limit).to_a
- 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)}]")
+ end
+
+ def find_nth_with_limit(offset, limit)
+ relation = if order_values.empty? && primary_key
+ order(arel_table[primary_key].asc)
+ else
+ self
+ end
+
+ relation = relation.offset(offset) unless offset.zero?
+ relation.limit(limit).to_a
end
def find_last