diff options
Diffstat (limited to 'activerecord/lib/active_record/relation/finder_methods.rb')
-rw-r--r-- | activerecord/lib/active_record/relation/finder_methods.rb | 146 |
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) |