aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/relation.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/relation.rb')
-rw-r--r--activerecord/lib/active_record/relation.rb174
1 files changed, 80 insertions, 94 deletions
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 7e842668c6..ef629dcb3b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,5 +1,3 @@
-require "arel/collectors/bind"
-
module ActiveRecord
# = Active Record \Relation
class Relation
@@ -31,9 +29,7 @@ module ActiveRecord
end
def initialize_copy(other)
- # This method is a hot spot, so for now, use Hash[] to dup the hash.
- # https://bugs.ruby-lang.org/issues/7166
- @values = Hash[@values]
+ @values = @values.dup
reset
end
@@ -45,8 +41,8 @@ module ActiveRecord
k.name == primary_key
}]
- if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
- primary_key_value = connection.next_sequence_value(klass.sequence_name)
+ if !primary_key_value && klass.prefetch_primary_key?
+ primary_key_value = klass.next_sequence_value
values[arel_attribute(klass.primary_key)] = primary_key_value
end
end
@@ -64,8 +60,8 @@ module ActiveRecord
@klass.connection.insert(
im,
- 'SQL',
- primary_key,
+ "SQL",
+ primary_key || false,
primary_key_value,
nil,
binds)
@@ -88,18 +84,18 @@ module ActiveRecord
@klass.connection.update(
um,
- 'SQL',
+ "SQL",
bvs,
)
end
def substitute_values(values) # :nodoc:
- binds = values.map do |arel_attr, value|
- QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
- end
+ binds = []
+ substitutes = []
- substitutes = values.map do |(arel_attr, _)|
- [arel_attr, Arel::Nodes::BindParam.new]
+ values.each do |arel_attr, value|
+ binds.push QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
+ substitutes.push [arel_attr, Arel::Nodes::BindParam.new]
end
[substitutes, binds]
@@ -247,23 +243,26 @@ module ActiveRecord
# Please see further details in the
# {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
def explain
- #TODO: Fix for binds.
exec_explain(collecting_queries_for_explain { exec_queries })
end
# Converts relation objects to Array.
def to_a
+ records.dup
+ end
+
+ def records # :nodoc:
load
@records
end
# Serializes the relation objects Array.
def encode_with(coder)
- coder.represent_seq(nil, to_a)
+ coder.represent_seq(nil, records)
end
def as_json(options = nil) #:nodoc:
- to_a.as_json(options)
+ records.as_json(options)
end
# Returns size of the records.
@@ -275,12 +274,7 @@ module ActiveRecord
def empty?
return @records.empty? if loaded?
- if limit_value == 0
- true
- else
- c = count(:all)
- c.respond_to?(:zero?) ? c.zero? : c.empty?
- end
+ limit_value == 0 || !exists?
end
# Returns true if there are no records.
@@ -298,13 +292,13 @@ module ActiveRecord
# Returns true if there is exactly one record.
def one?
return super if block_given?
- limit_value ? to_a.one? : size == 1
+ limit_value ? records.one? : size == 1
end
# Returns true if there is more than one record.
def many?
return super if block_given?
- limit_value ? to_a.many? : size > 1
+ limit_value ? records.many? : size > 1
end
# Returns a cache key that can be used to identify the records fetched by
@@ -385,7 +379,7 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
- @klass.connection.update stmt, 'SQL', bound_attributes
+ @klass.connection.update stmt, "SQL", bound_attributes
end
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -418,13 +412,13 @@ module ActiveRecord
if id.is_a?(Array)
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
elsif id == :all
- to_a.each { |record| record.update(attributes) }
+ records.each { |record| record.update(attributes) }
else
if ActiveRecord::Base === id
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `update`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
object = find(id)
@@ -453,11 +447,11 @@ module ActiveRecord
if conditions
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
Passing conditions to destroy_all is deprecated and will be removed in Rails 5.1.
- To achieve the same use where(conditions).destroy_all
+ To achieve the same use where(conditions).destroy_all.
MESSAGE
where(conditions).destroy_all
else
- to_a.each(&:destroy).tap { reset }
+ records.each(&:destroy).tap { reset }
end
end
@@ -507,15 +501,10 @@ module ActiveRecord
# Post.limit(100).delete_all
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit
def delete_all(conditions = nil)
- invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method|
- if MULTI_VALUE_METHODS.include?(method)
- send("#{method}_values").any?
- elsif SINGLE_VALUE_METHODS.include?(method)
- send("#{method}_value")
- elsif CLAUSE_METHODS.include?(method)
- send("#{method}_clause").any?
- end
- }
+ invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
+ value = get_value(method)
+ SINGLE_VALUE_METHODS.include?(method) ? value : value.any?
+ end
if invalid_methods.any?
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
end
@@ -523,7 +512,7 @@ module ActiveRecord
if conditions
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
Passing conditions to delete_all is deprecated and will be removed in Rails 5.1.
- To achieve the same use where(conditions).delete_all
+ To achieve the same use where(conditions).delete_all.
MESSAGE
where(conditions).delete_all
else
@@ -536,7 +525,7 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
- affected = @klass.connection.delete(stmt, 'SQL', bound_attributes)
+ affected = @klass.connection.delete(stmt, "SQL", bound_attributes)
reset
affected
@@ -572,8 +561,8 @@ module ActiveRecord
# return value is the relation itself, not the records.
#
# Post.where(published: true).load # => #<ActiveRecord::Relation>
- def load
- exec_queries unless loaded?
+ def load(&block)
+ exec_queries(&block) unless loaded?
self
end
@@ -587,7 +576,7 @@ module ActiveRecord
def reset
@last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
@should_eager_load = @join_dependency = nil
- @records = []
+ @records = [].freeze
@offsets = {}
self
end
@@ -598,19 +587,16 @@ module ActiveRecord
# # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
def to_sql
@to_sql ||= begin
- relation = self
- connection = klass.connection
- visitor = connection.visitor
+ relation = self
if eager_loading?
find_with_associations { |rel| relation = rel }
end
- binds = relation.bound_attributes
- binds = connection.prepare_binds_for_database(binds)
- binds.map! { |value| connection.quote(value) }
- collect = visitor.accept(relation.arel.ast, Arel::Collectors::Bind.new)
- collect.substitute_binds(binds).join
+ conn = klass.connection
+ conn.unprepared_statement {
+ conn.to_sql(relation.arel, relation.bound_attributes)
+ }
end
end
@@ -654,30 +640,30 @@ module ActiveRecord
def ==(other)
case other
when Associations::CollectionProxy, AssociationRelation
- self == other.to_a
+ self == other.records
when Relation
other.to_sql == to_sql
when Array
- to_a == other
+ records == other
end
end
def pretty_print(q)
- q.pp(self.to_a)
+ q.pp(records)
end
# Returns true if relation is blank.
def blank?
- to_a.blank?
+ records.blank?
end
def values
- Hash[@values]
+ @values.dup
end
def inspect
- entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
- entries[10] = '...' if entries.size == 11
+ entries = records.take([limit_value, 11].compact.min).map!(&:inspect)
+ entries[10] = "..." if entries.size == 11
"#<#{self.class.name} [#{entries.join(', ')}]>"
end
@@ -685,54 +671,54 @@ module ActiveRecord
protected
def load_records(records)
- @records = records
+ @records = records.freeze
@loaded = true
end
private
- def exec_queries
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bound_attributes)
+ def exec_queries(&block)
+ @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes, &block).freeze
- preload = preload_values
- preload += includes_values unless eager_loading?
- preloader = build_preloader
- preload.each do |associations|
- preloader.preload @records, associations
- end
+ preload = preload_values
+ preload += includes_values unless eager_loading?
+ preloader = build_preloader
+ preload.each do |associations|
+ preloader.preload @records, associations
+ end
- @records.each(&:readonly!) if readonly_value
+ @records.each(&:readonly!) if readonly_value
- @loaded = true
- @records
- end
+ @loaded = true
+ @records
+ end
- def build_preloader
- ActiveRecord::Associations::Preloader.new
- end
+ def build_preloader
+ ActiveRecord::Associations::Preloader.new
+ end
- def references_eager_loaded_tables?
- joined_tables = arel.join_sources.map do |join|
- if join.is_a?(Arel::Nodes::StringJoin)
- tables_in_string(join.left)
- else
- [join.left.table_name, join.left.table_alias]
+ def references_eager_loaded_tables?
+ joined_tables = arel.join_sources.map do |join|
+ if join.is_a?(Arel::Nodes::StringJoin)
+ tables_in_string(join.left)
+ else
+ [join.left.table_name, join.left.table_alias]
+ end
end
- end
- joined_tables += [table.name, table.table_alias]
+ joined_tables += [table.name, table.table_alias]
- # always convert table names to downcase as in Oracle quoted table names are in uppercase
- joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq
+ # always convert table names to downcase as in Oracle quoted table names are in uppercase
+ joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq
- (references_values - joined_tables).any?
- end
+ (references_values - joined_tables).any?
+ end
- def tables_in_string(string)
- return [] if string.blank?
- # always convert table names to downcase as in Oracle quoted table names are in uppercase
- # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
- string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ['raw_sql_']
- end
+ def tables_in_string(string)
+ return [] if string.blank?
+ # always convert table names to downcase as in Oracle quoted table names are in uppercase
+ # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
+ string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"]
+ end
end
end