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.rb146
1 files changed, 71 insertions, 75 deletions
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 0c9c761f97..435cef901b 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -1,11 +1,11 @@
-require 'active_support/deprecation'
+require 'active_support/core_ext/string/filters'
module ActiveRecord
module FinderMethods
ONE_AS_ONE = '1 AS one'
# Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
- # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
+ # If one or more records can not be found for the requested ids, then RecordNotFound will be raised. If the primary key
# is an integer, find by id coerces its arguments using +to_i+.
#
# Person.find(1) # returns the object for ID = 1
@@ -16,10 +16,8 @@ module ActiveRecord
# Person.find([1]) # returns an array for the object with ID = 1
# Person.where("administrator = 1").order("created_on DESC").find(1)
#
- # <tt>ActiveRecord::RecordNotFound</tt> will be raised if one or more ids are not found.
- #
# NOTE: The returned records may not be in the same order as the ids you
- # provide since database rows are unordered. You'd need to provide an explicit <tt>order</tt>
+ # provide since database rows are unordered. You'd need to provide an explicit QueryMethods#order
# option if you want the results are sorted.
#
# ==== Find with lock
@@ -36,7 +34,7 @@ module ActiveRecord
# person.save!
# end
#
- # ==== Variations of +find+
+ # ==== Variations of #find
#
# Person.where(name: 'Spartacus', rating: 4)
# # returns a chainable list (which can be empty).
@@ -48,9 +46,9 @@ module ActiveRecord
# # returns the first item or returns a new instance (requires you call .save to persist against the database).
#
# Person.where(name: 'Spartacus', rating: 4).first_or_create
- # # returns the first item or creates it and returns it, available since Rails 3.2.1.
+ # # returns the first item or creates it and returns it.
#
- # ==== Alternatives for +find+
+ # ==== Alternatives for #find
#
# Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none)
# # returns a boolean indicating if any record with the given conditions exist.
@@ -59,16 +57,13 @@ module ActiveRecord
# # returns a chainable list of instances with only the mentioned fields.
#
# Person.where(name: 'Spartacus', rating: 4).ids
- # # returns an Array of ids, available since Rails 3.2.1.
+ # # returns an Array of ids.
#
# Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
- # # returns an Array of the required fields, available since Rails 3.1.
+ # # returns an Array of the required fields.
def find(*args)
- if block_given?
- to_a.find(*args) { |*block_args| yield(*block_args) }
- else
- find_with_ids(*args)
- end
+ return super if block_given?
+ find_with_ids(*args)
end
# Finds the first record matching the specified conditions. There
@@ -79,14 +74,19 @@ module ActiveRecord
#
# Post.find_by name: 'Spartacus', rating: 4
# Post.find_by "published_at < ?", 2.weeks.ago
- def find_by(*args)
- where(*args).take
+ def find_by(arg, *args)
+ where(arg, *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!
+ # Like #find_by, except that if no record is found, raises
+ # an ActiveRecord::RecordNotFound error.
+ def find_by!(arg, *args)
+ where(arg, *args).take!
+ rescue RangeError
+ raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
+ @klass.name)
end
# Gives a record (or N records if a parameter is supplied) without any implied
@@ -100,32 +100,20 @@ module ActiveRecord
limit ? limit(limit).to_a : find_take
end
- # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
- # is found. Note that <tt>take!</tt> accepts no arguments.
+ # Same as #take but raises ActiveRecord::RecordNotFound if no record
+ # is found. Note that #take! 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).
# If no order is defined it will order by primary key.
#
- # Person.first # returns the first object fetched by SELECT * FROM people
+ # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1
# Person.where(["user_name = ?", user_name]).first
# Person.where(["user_name = :u", { u: user_name }]).first
# Person.order("created_on DESC").offset(5).first
- # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
- #
- # ==== Rails 3
- #
- # Person.first # SELECT "people".* FROM "people" LIMIT 1
- #
- # NOTE: Rails 3 may not order this query by the primary key and the order
- # will depend on the database implementation. In order to ensure that behavior,
- # use <tt>User.order(:id).first</tt> instead.
- #
- # ==== Rails 4
- #
- # Person.first # SELECT "people".* FROM "people" ORDER BY "people"."id" ASC LIMIT 1
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
#
def first(limit = nil)
if limit
@@ -135,10 +123,10 @@ module ActiveRecord
end
end
- # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
- # is found. Note that <tt>first!</tt> accepts no arguments.
+ # Same as #first but raises ActiveRecord::RecordNotFound if no record
+ # is found. Note that #first! 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).
@@ -168,10 +156,10 @@ module ActiveRecord
end
end
- # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
- # is found. Note that <tt>last!</tt> accepts no arguments.
+ # Same as #last but raises ActiveRecord::RecordNotFound if no record
+ # is found. Note that #last! 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.
@@ -184,10 +172,10 @@ module ActiveRecord
find_nth(1, offset_index)
end
- # Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # Same as #second but raises ActiveRecord::RecordNotFound if no record
# is found.
def second!
- second or raise RecordNotFound
+ find_nth! 1
end
# Find the third record.
@@ -200,10 +188,10 @@ module ActiveRecord
find_nth(2, offset_index)
end
- # Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # Same as #third but raises ActiveRecord::RecordNotFound if no record
# is found.
def third!
- third or raise RecordNotFound
+ find_nth! 2
end
# Find the fourth record.
@@ -216,10 +204,10 @@ module ActiveRecord
find_nth(3, offset_index)
end
- # Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # Same as #fourth but raises ActiveRecord::RecordNotFound if no record
# is found.
def fourth!
- fourth or raise RecordNotFound
+ find_nth! 3
end
# Find the fifth record.
@@ -232,10 +220,10 @@ module ActiveRecord
find_nth(4, offset_index)
end
- # Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # Same as #fifth but raises ActiveRecord::RecordNotFound if no record
# is found.
def fifth!
- fifth or raise RecordNotFound
+ find_nth! 4
end
# Find the forty-second record. Also known as accessing "the reddit".
@@ -248,14 +236,14 @@ module ActiveRecord
find_nth(41, offset_index)
end
- # Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
# is found.
def forty_two!
- forty_two or raise RecordNotFound
+ find_nth! 41
end
- # Returns +true+ if a record exists in the table that matches the +id+ or
- # conditions given, or +false+ otherwise. The argument can take six forms:
+ # Returns true if a record exists in the table that matches the +id+ or
+ # conditions given, or false otherwise. The argument can take six forms:
#
# * Integer - Finds the record with this primary key.
# * String - Finds the record with a primary key corresponding to this
@@ -268,7 +256,7 @@ module ActiveRecord
# * No args - Returns +false+ if the table is empty, +true+ otherwise.
#
# For more information about specifying conditions as a hash or array,
- # see the Conditions section in the introduction to <tt>ActiveRecord::Base</tt>.
+ # see the Conditions section in the introduction to ActiveRecord::Base.
#
# Note: You can't pass in a condition as a string (like <tt>name =
# 'Jamie'</tt>), since it would be sanitized and then queried against
@@ -284,8 +272,10 @@ module ActiveRecord
def exists?(conditions = :none)
if Base === conditions
conditions = conditions.id
- ActiveSupport::Deprecation.warn "You are passing an instance of ActiveRecord::Base to `exists?`." \
- "Please pass the id of the object by calling `.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
@@ -300,15 +290,15 @@ module ActiveRecord
relation = relation.where(conditions)
else
unless conditions == :none
- relation = where(primary_key => conditions)
+ relation = 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.bound_attributes) ? true : false
end
# This method is called whenever no records are found with either a single
- # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
+ # id or multiple ids and raises a ActiveRecord::RecordNotFound exception.
#
# The error message is different depending on whether a single id or
# multiple ids are provided. If multiple ids are provided, then the number
@@ -316,7 +306,7 @@ 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
@@ -358,7 +348,7 @@ module ActiveRecord
[]
else
arel = relation.arel
- rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
+ rows = connection.select_all(arel, 'SQL', relation.bound_attributes)
join_dependency.instantiate(rows, aliases)
end
end
@@ -372,7 +362,7 @@ module ActiveRecord
def construct_relation_for_association_calculations
from = arel.froms.first
if Arel::Table === from
- apply_join_dependency(self, construct_join_dependency)
+ apply_join_dependency(self, construct_join_dependency(joins_values))
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
@@ -390,7 +380,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
@@ -401,13 +391,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', relation.bound_attributes)
id_rows.map {|row| row[primary_key]}
end
def using_limitable_reflections?(reflections)
- reflections.none? { |r| r.collection? }
+ reflections.none?(&:collection?)
end
protected
@@ -429,19 +420,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)
if ActiveRecord::Base === id
id = id.id
- ActiveSupport::Deprecation.warn "You are passing an instance of ActiveRecord::Base to `find`." \
- "Please pass the id of the object by calling `.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
@@ -450,7 +442,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
@@ -488,6 +480,10 @@ module ActiveRecord
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)}]")
+ end
+
def find_nth_with_limit(offset, limit)
relation = if order_values.empty? && primary_key
order(arel_table[primary_key].asc)