diff options
Diffstat (limited to 'activerecord/lib/active_record/relation/finder_methods.rb')
-rw-r--r-- | activerecord/lib/active_record/relation/finder_methods.rb | 117 |
1 files changed, 57 insertions, 60 deletions
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index a1459c87c6..ff06ecbee1 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/string/filters" module ActiveRecord @@ -16,9 +18,10 @@ module ActiveRecord # Person.find([1]) # returns an array for the object with ID = 1 # Person.where("administrator = 1").order("created_on DESC").find(1) # - # NOTE: The returned records may not be in the same order as the ids you - # provide since database rows are unordered. You will need to provide an explicit QueryMethods#order - # option if you want the results to be sorted. + # NOTE: The returned records are in the same order as the ids you provide. + # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where + # method and provide an explicit ActiveRecord::QueryMethods#order option. + # But ActiveRecord::QueryMethods#where method doesn't raise ActiveRecord::RecordNotFound. # # ==== Find with lock # @@ -86,7 +89,7 @@ module ActiveRecord where(arg, *args).take! rescue ::RangeError raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value", - @klass.name) + @klass.name, @klass.primary_key) end # Gives a record (or N records if a parameter is supplied) without any implied @@ -147,8 +150,7 @@ module ActiveRecord def last(limit = nil) return find_last(limit) if loaded? || limit_value - result = limit(limit) - result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key + result = ordered_relation.limit(limit) result = result.reverse_order! limit ? result.reverse : result.first @@ -283,7 +285,7 @@ module ActiveRecord # * Hash - Finds the record that matches these +find+-style conditions # (such as <tt>{name: 'David'}</tt>). # * +false+ - Returns always +false+. - # * No args - Returns +false+ if the table is empty, +true+ otherwise. + # * No args - Returns +false+ if the relation is empty, +true+ otherwise. # # For more information about specifying conditions as a hash or array, # see the Conditions section in the introduction to ActiveRecord::Base. @@ -299,6 +301,7 @@ module ActiveRecord # Person.exists?(name: 'David') # Person.exists?(false) # Person.exists? + # Person.where(name: 'Spartacus', rating: 4).exists? def exists?(conditions = :none) if Base === conditions raise ArgumentError, <<-MSG.squish @@ -307,14 +310,16 @@ module ActiveRecord MSG end - return false if !conditions + return false if !conditions || limit_value == 0 - relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false)) - return false if ActiveRecord::NullRelation === relation + if eager_loading? + relation = apply_join_dependency(construct_join_dependency(eager_loading: false)) + return relation.exists?(conditions) + end - relation = construct_relation_for_exists(relation, conditions) + relation = construct_relation_for_exists(conditions) - connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false + skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false rescue ::RangeError false end @@ -327,23 +332,23 @@ module ActiveRecord # of results obtained should be provided in the +result_size+ argument and # the expected number of results should be provided in the +expected_size+ # argument. - def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key) # :nodoc: - conditions = arel.where_sql(@klass.arel_engine) + def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc: + conditions = arel.where_sql(@klass) conditions = " [#{conditions}]" if conditions name = @klass.name if ids.nil? - error = "Couldn't find #{name}" + error = "Couldn't find #{name}".dup error << " with#{conditions}" if conditions - raise RecordNotFound.new(error, name) + raise RecordNotFound.new(error, name, key) elsif Array(ids).size == 1 error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}" raise RecordNotFound.new(error, name, key, ids) else - error = "Couldn't find all #{name.pluralize} with '#{key}': " - error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})" - - raise RecordNotFound.new(error, name, primary_key, ids) + error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup + error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})." + error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids + raise RecordNotFound.new(error, name, key, ids) end end @@ -363,27 +368,16 @@ module ActiveRecord # preexisting join in joins_values to categorizations (by way of # the `has_many :through` for categories). # - join_dependency = construct_join_dependency(joins_values) + join_dependency = construct_join_dependency - aliases = join_dependency.aliases - relation = select aliases.columns - relation = apply_join_dependency(relation, join_dependency) + relation = apply_join_dependency(join_dependency) + relation._select!(join_dependency.aliases.columns) - if block_given? - yield relation - else - if ActiveRecord::NullRelation === relation - [] - else - arel = relation.arel - rows = connection.select_all(arel, "SQL", relation.bound_attributes) - join_dependency.instantiate(rows, aliases) - end - end + yield relation, join_dependency end - def construct_relation_for_exists(relation, conditions) - relation = relation.except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) + def construct_relation_for_exists(conditions) + relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) case conditions when Array, Hash @@ -395,17 +389,15 @@ module ActiveRecord relation end - def construct_join_dependency(joins = [], eager_loading: true) + def construct_join_dependency(eager_loading: true) including = eager_load_values + includes_values - ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading) - end - - def construct_relation_for_association_calculations - apply_join_dependency(self, construct_join_dependency(joins_values)) + ActiveRecord::Associations::JoinDependency.new( + klass, table, including, alias_tracker(joins_values), eager_loading: eager_loading + ) end - def apply_join_dependency(relation, join_dependency) - relation = relation.except(:includes, :eager_load, :preload).joins!(join_dependency) + def apply_join_dependency(join_dependency = construct_join_dependency) + relation = except(:includes, :eager_load, :preload).joins!(join_dependency) if using_limitable_reflections?(join_dependency.reflections) relation @@ -420,12 +412,13 @@ module ActiveRecord def limited_ids_for(relation) values = @klass.connection.columns_for_distinct( - "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values) + connection.column_name_from_arel_node(arel_attribute(primary_key)), + relation.order_values + ) relation = relation.except(:select).select(values).distinct! - arel = relation.arel - id_rows = @klass.connection.select_all(arel, "SQL", relation.bound_attributes) + id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL") } id_rows.map { |row| row[primary_key] } end @@ -441,9 +434,12 @@ module ActiveRecord ids = ids.flatten.compact.uniq + model_name = @klass.name + case ids.size when 0 - raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" + error_message = "Couldn't find #{model_name} without an ID" + raise RecordNotFound.new(error_message, model_name, primary_key) when 1 result = find_one(ids.first) expects_array ? [ result ] : result @@ -451,7 +447,8 @@ module ActiveRecord find_some(ids) end rescue ::RangeError - raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID" + error_message = "Couldn't find #{model_name} with an out of range ID" + raise RecordNotFound.new(error_message, model_name, primary_key, ids) end def find_one(id) @@ -533,11 +530,7 @@ module ActiveRecord if loaded? records[index, limit] || [] else - relation = if order_values.empty? && primary_key - order(arel_attribute(primary_key).asc) - else - self - end + relation = ordered_relation if limit_value.nil? || index < limit_value relation = relation.offset(offset_index + index) unless index.zero? @@ -552,11 +545,7 @@ module ActiveRecord if loaded? records[-index] else - relation = if order_values.empty? && primary_key - order(arel_attribute(primary_key).asc) - else - self - end + relation = ordered_relation relation.to_a[-index] # TODO: can be made more performant on large result sets by @@ -570,5 +559,13 @@ module ActiveRecord def find_last(limit) limit ? records.last(limit) : records.last end + + def ordered_relation + if order_values.empty? && primary_key + order(arel_attribute(primary_key).asc) + else + self + end + end end end |