aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/associations
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/associations')
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb2
-rw-r--r--activerecord/lib/active_record/associations/association.rb25
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb160
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb30
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb56
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb274
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb193
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb30
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb19
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb50
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb234
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_base.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_part.rb6
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb188
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb160
-rw-r--r--activerecord/lib/active_record/associations/preloader/belongs_to.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/collection_association.rb13
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/singular_association.rb15
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb69
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb41
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb7
27 files changed, 767 insertions, 834 deletions
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 021bc32237..3963008a76 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/string/conversions'
+require "active_support/core_ext/string/conversions"
module ActiveRecord
module Associations
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 62e867a353..84d0493a60 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/array/wrap'
+require "active_support/core_ext/array/wrap"
module ActiveRecord
module Associations
@@ -19,7 +19,7 @@ module ActiveRecord
attr_reader :owner, :target, :reflection
attr_accessor :inversed
- delegate :options, :to => :reflection
+ delegate :options, to: :reflection
def initialize(owner, reflection)
reflection.check_validity!
@@ -112,6 +112,15 @@ module ActiveRecord
record
end
+ # Remove the inverse association, if possible
+ def remove_inverse_instance(record)
+ if invertible_for?(record)
+ inverse = record.association(inverse_reflection_for(record).name)
+ inverse.target = nil
+ inverse.inversed = false
+ end
+ end
+
# Returns the class of the target. belongs_to polymorphic overrides this to look at the
# polymorphic_type field on the owner.
def klass
@@ -166,13 +175,21 @@ module ActiveRecord
def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
except_from_scope_attributes ||= {}
skip_assign = [reflection.foreign_key, reflection.type].compact
- assigned_keys = record.changed
+ assigned_keys = record.changed_attribute_names_to_save
assigned_keys += except_from_scope_attributes.keys.map(&:to_s)
attributes = create_scope.except(*(assigned_keys - skip_assign))
record.assign_attributes(attributes)
set_inverse_instance(record)
end
+ def create(attributes = {}, &block)
+ _create_record(attributes, &block)
+ end
+
+ def create!(attributes = {}, &block)
+ _create_record(attributes, true, &block)
+ end
+
private
def find_target?
@@ -246,7 +263,7 @@ module ActiveRecord
# so that when stale_state is different from the value stored on the last find_target,
# the target is stale.
#
- # This is only relevant to certain associations, which is why it returns nil by default.
+ # This is only relevant to certain associations, which is why it returns +nil+ by default.
def stale_state
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 15844de0bc..c6d204d3c2 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -49,117 +49,119 @@ module ActiveRecord
binds
end
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
protected
- attr_reader :value_transformation
+ attr_reader :value_transformation
private
- def join(table, constraint)
- table.create_join(table, table.create_on(constraint), join_type)
- end
+ def join(table, constraint)
+ table.create_join(table, table.create_on(constraint), join_type)
+ end
+
+ def last_chain_scope(scope, table, reflection, owner, association_klass)
+ join_keys = reflection.join_keys(association_klass)
+ key = join_keys.key
+ foreign_key = join_keys.foreign_key
- def last_chain_scope(scope, table, reflection, owner, association_klass)
- join_keys = reflection.join_keys(association_klass)
- key = join_keys.key
- foreign_key = join_keys.foreign_key
+ value = transform_value(owner[foreign_key])
+ scope = scope.where(table.name => { key => value })
- value = transform_value(owner[foreign_key])
- scope = scope.where(table.name => { key => value })
+ if reflection.type
+ polymorphic_type = transform_value(owner.class.base_class.name)
+ scope = scope.where(table.name => { reflection.type => polymorphic_type })
+ end
- if reflection.type
- polymorphic_type = transform_value(owner.class.base_class.name)
- scope = scope.where(table.name => { reflection.type => polymorphic_type })
+ scope
end
- scope
- end
+ def transform_value(value)
+ value_transformation.call(value)
+ end
- def transform_value(value)
- value_transformation.call(value)
- end
+ def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
+ join_keys = reflection.join_keys(association_klass)
+ key = join_keys.key
+ foreign_key = join_keys.foreign_key
- def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
- join_keys = reflection.join_keys(association_klass)
- key = join_keys.key
- foreign_key = join_keys.foreign_key
+ constraint = table[key].eq(foreign_table[foreign_key])
- constraint = table[key].eq(foreign_table[foreign_key])
+ if reflection.type
+ value = transform_value(next_reflection.klass.base_class.name)
+ scope = scope.where(table.name => { reflection.type => value })
+ end
- if reflection.type
- value = transform_value(next_reflection.klass.base_class.name)
- scope = scope.where(table.name => { reflection.type => value })
+ scope = scope.joins(join(foreign_table, constraint))
end
- scope = scope.joins(join(foreign_table, constraint))
- end
+ class ReflectionProxy < SimpleDelegator # :nodoc:
+ attr_accessor :next
+ attr_reader :alias_name
- class ReflectionProxy < SimpleDelegator # :nodoc:
- attr_accessor :next
- attr_reader :alias_name
+ def initialize(reflection, alias_name)
+ super(reflection)
+ @alias_name = alias_name
+ end
- def initialize(reflection, alias_name)
- super(reflection)
- @alias_name = alias_name
+ def all_includes; nil; end
end
- def all_includes; nil; end
- end
-
- def get_chain(reflection, association, tracker)
- name = reflection.name
- runtime_reflection = Reflection::RuntimeReflection.new(reflection, association)
- previous_reflection = runtime_reflection
- reflection.chain.drop(1).each do |refl|
- alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name))
- proxy = ReflectionProxy.new(refl, alias_name)
- previous_reflection.next = proxy
- previous_reflection = proxy
+ def get_chain(reflection, association, tracker)
+ name = reflection.name
+ runtime_reflection = Reflection::RuntimeReflection.new(reflection, association)
+ previous_reflection = runtime_reflection
+ reflection.chain.drop(1).each do |refl|
+ alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name))
+ proxy = ReflectionProxy.new(refl, alias_name)
+ previous_reflection.next = proxy
+ previous_reflection = proxy
+ end
+ [runtime_reflection, previous_reflection]
end
- [runtime_reflection, previous_reflection]
- end
- def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail)
- owner_reflection = chain_tail
- table = owner_reflection.alias_name
- scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass)
+ def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail)
+ owner_reflection = chain_tail
+ table = owner_reflection.alias_name
+ scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass)
- reflection = chain_head
- while reflection
- table = reflection.alias_name
+ reflection = chain_head
+ while reflection
+ table = reflection.alias_name
- unless reflection == chain_tail
- next_reflection = reflection.next
- foreign_table = next_reflection.alias_name
- scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
- end
+ unless reflection == chain_tail
+ next_reflection = reflection.next
+ foreign_table = next_reflection.alias_name
+ scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
+ end
- # Exclude the scope of the association itself, because that
- # was already merged in the #scope method.
- reflection.constraints.each do |scope_chain_item|
- item = eval_scope(reflection.klass, scope_chain_item, owner)
+ # Exclude the scope of the association itself, because that
+ # was already merged in the #scope method.
+ reflection.constraints.each do |scope_chain_item|
+ item = eval_scope(reflection.klass, scope_chain_item, owner)
- if scope_chain_item == refl.scope
- scope.merge! item.except(:where, :includes)
- end
+ if scope_chain_item == refl.scope
+ scope.merge! item.except(:where, :includes)
+ end
+
+ reflection.all_includes do
+ scope.includes! item.includes_values
+ end
- reflection.all_includes do
- scope.includes! item.includes_values
+ scope.unscope!(*item.unscope_values)
+ scope.where_clause += item.where_clause
+ scope.order_values |= item.order_values
end
- scope.unscope!(*item.unscope_values)
- scope.where_clause += item.where_clause
- scope.order_values |= item.order_values
+ reflection = reflection.next
end
- reflection = reflection.next
+ scope
end
- scope
- end
-
- def eval_scope(klass, scope, owner)
- klass.unscoped.instance_exec(owner, &scope)
- end
+ def eval_scope(klass, scope, owner)
+ klass.unscoped.instance_exec(owner, &scope)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 24997370b2..64b2311911 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -2,7 +2,6 @@ module ActiveRecord
# = Active Record Belongs To Association
module Associations
class BelongsToAssociation < SingularAssociation #:nodoc:
-
def handle_dependency
target.send(options[:dependent]) if load_target
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 3121e70a04..a1609ab0fb 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -35,17 +35,17 @@ module ActiveRecord::Associations::Builder # :nodoc:
@_after_create_counter_called = false
elsif (@_after_replace_counter_called ||= false)
@_after_replace_counter_called = false
- elsif attribute_changed?(foreign_key) && !new_record?
+ elsif saved_change_to_attribute?(foreign_key) && !new_record?
if reflection.polymorphic?
- model = attribute(reflection.foreign_type).try(:constantize)
- model_was = attribute_was(reflection.foreign_type).try(:constantize)
+ model = attribute_in_database(reflection.foreign_type).try(:constantize)
+ model_was = attribute_before_last_save(reflection.foreign_type).try(:constantize)
else
model = reflection.klass
model_was = reflection.klass
end
- foreign_key_was = attribute_was foreign_key
- foreign_key = attribute foreign_key
+ foreign_key_was = attribute_before_last_save foreign_key
+ foreign_key = attribute_in_database foreign_key
if foreign_key && model.respond_to?(:increment_counter)
model.increment_counter(cache_column, foreign_key)
@@ -70,14 +70,16 @@ module ActiveRecord::Associations::Builder # :nodoc:
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
end
- def self.touch_record(o, foreign_key, name, touch, touch_method) # :nodoc:
- old_foreign_id = o.changed_attributes[foreign_key]
+ def self.touch_record(o, changes, foreign_key, name, touch, touch_method) # :nodoc:
+ old_foreign_id = changes[foreign_key] && changes[foreign_key].first
if old_foreign_id
association = o.association(name)
reflection = association.reflection
if reflection.polymorphic?
- klass = o.public_send("#{reflection.foreign_type}_was").constantize
+ foreign_type = reflection.foreign_type
+ klass = changes[foreign_type] && changes[foreign_type].first || o.public_send(foreign_type)
+ klass = klass.constantize
else
klass = association.klass
end
@@ -107,13 +109,13 @@ module ActiveRecord::Associations::Builder # :nodoc:
n = reflection.name
touch = reflection.options[:touch]
- callback = lambda { |record|
- BelongsTo.touch_record(record, foreign_key, n, touch, belongs_to_touch_method)
- }
+ callback = lambda { |changes_method| lambda { |record|
+ BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
+ }}
- model.after_save callback, if: :changed?
- model.after_touch callback
- model.after_destroy callback
+ model.after_save callback.(:saved_changes), if: :saved_changes?
+ model.after_touch callback.(:changes_to_save)
+ model.after_destroy callback.(:changes_to_save)
end
def self.add_destroy_callbacks(model, reflection)
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index f25bd7ca9f..edeb6491bd 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,10 +1,9 @@
# This class is inherited by the has_many and has_many_and_belongs_to_many association classes
-require 'active_record/associations'
+require "active_record/associations"
module ActiveRecord::Associations::Builder # :nodoc:
class CollectionAssociation < Association #:nodoc:
-
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
def self.valid_options(options)
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 5fbd79d118..6b71826431 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -16,9 +16,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
private
- def klass
- @lhs_class.send(:compute_type, @rhs_class_name)
- end
+ def klass
+ @lhs_class.send(:compute_type, @rhs_class_name)
+ end
end
def self.build(lhs_class, name, options)
@@ -28,7 +28,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
class_name = options.fetch(:class_name) {
name.to_s.camelize.singularize
}
- KnownClass.new lhs_class, class_name
+ KnownClass.new lhs_class, class_name.to_s
end
end
end
@@ -76,9 +76,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
left_model.retrieve_connection
end
- def self.primary_key
- false
- end
+ private
+
+ def self.suppress_composite_primary_key(pk)
+ pk unless pk.is_a?(Array)
+ end
}
join_model.name = "HABTM_#{association_name.to_s.camelize}"
@@ -92,7 +94,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
def middle_reflection(join_model)
middle_name = [lhs_model.name.downcase.pluralize,
- association_name].join('_'.freeze).gsub('::'.freeze, '_'.freeze).to_sym
+ association_name].join("_".freeze).gsub("::".freeze, "_".freeze).to_sym
middle_options = middle_options join_model
HasMany.create_reflection(lhs_model,
@@ -103,29 +105,29 @@ module ActiveRecord::Associations::Builder # :nodoc:
private
- def middle_options(join_model)
- middle_options = {}
- middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
- middle_options[:source] = join_model.left_reflection.name
- if options.key? :foreign_key
- middle_options[:foreign_key] = options[:foreign_key]
+ def middle_options(join_model)
+ middle_options = {}
+ middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
+ middle_options[:source] = join_model.left_reflection.name
+ if options.key? :foreign_key
+ middle_options[:foreign_key] = options[:foreign_key]
+ end
+ middle_options
end
- middle_options
- end
- def belongs_to_options(options)
- rhs_options = {}
+ def belongs_to_options(options)
+ rhs_options = {}
- if options.key? :class_name
- rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
- rhs_options[:class_name] = options[:class_name]
- end
+ if options.key? :class_name
+ rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
+ rhs_options[:class_name] = options[:class_name]
+ end
- if options.key? :association_foreign_key
- rhs_options[:foreign_key] = options[:association_foreign_key]
- end
+ if options.key? :association_foreign_key
+ rhs_options[:foreign_key] = options[:association_foreign_key]
+ end
- rhs_options
- end
+ rhs_options
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index bb96202a22..7732b63af6 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -8,7 +8,16 @@ module ActiveRecord::Associations::Builder # :nodoc:
def self.define_accessors(model, reflection)
super
- define_constructors(model.generated_association_methods, reflection.name) if reflection.constructable?
+ mixin = model.generated_association_methods
+ name = reflection.name
+
+ define_constructors(mixin, name) if reflection.constructable?
+
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def reload_#{name}
+ association(:#{name}).force_reload_reader
+ end
+ CODE
end
# Defines the (build|create)_association methods for belongs_to or has_one association
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 0eaa0a4f36..0437a79b84 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -24,18 +24,9 @@ module ActiveRecord
# If you need to work on all current children, new and existing records,
# +load_target+ and the +loaded+ flag are your friends.
class CollectionAssociation < Association #:nodoc:
-
# Implements the reader method, e.g. foo.items for Foo.has_many :items
- def reader(force_reload = false)
- if force_reload
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing an argument to force an association to reload is now
- deprecated and will be removed in Rails 5.1. Please call `reload`
- on the result collection proxy instead.
- MSG
-
- klass.uncached { reload }
- elsif stale_target?
+ def reader
+ if stale_target?
reload
end
@@ -56,9 +47,7 @@ module ActiveRecord
# Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
def ids_reader
if loaded?
- load_target.map do |record|
- record.send(reflection.association_primary_key)
- end
+ target.pluck(reflection.association_primary_key)
else
@association_ids ||= (
column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
@@ -69,13 +58,17 @@ module ActiveRecord
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
- pk_type = reflection.primary_key_type
+ pk_type = reflection.association_primary_key_type
ids = Array(ids).reject(&:blank?)
ids.map! { |i| pk_type.cast(i) }
records = klass.where(reflection.association_primary_key => ids).index_by do |r|
r.send(reflection.association_primary_key)
- end.values_at(*ids)
- replace(records)
+ end.values_at(*ids).compact
+ if records.size != ids.size
+ klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, reflection.association_primary_key)
+ else
+ replace(records)
+ end
end
def reset
@@ -83,14 +76,6 @@ module ActiveRecord
@target = []
end
- def select(*fields)
- if block_given?
- load_target.select.each { |e| yield e }
- else
- scope.select(*fields)
- end
- end
-
def find(*args)
if block_given?
load_target.find(*args) { |*block_args| yield(*block_args) }
@@ -112,52 +97,6 @@ module ActiveRecord
end
end
- def first(*args)
- first_nth_or_last(:first, *args)
- end
-
- def second(*args)
- first_nth_or_last(:second, *args)
- end
-
- def third(*args)
- first_nth_or_last(:third, *args)
- end
-
- def fourth(*args)
- first_nth_or_last(:fourth, *args)
- end
-
- def fifth(*args)
- first_nth_or_last(:fifth, *args)
- end
-
- def forty_two(*args)
- first_nth_or_last(:forty_two, *args)
- end
-
- def third_to_last(*args)
- first_nth_or_last(:third_to_last, *args)
- end
-
- def second_to_last(*args)
- first_nth_or_last(:second_to_last, *args)
- end
-
- def last(*args)
- first_nth_or_last(:last, *args)
- end
-
- def take(n = nil)
- if loaded?
- n ? target.take(n) : target.first
- else
- scope.take(n).tap do |record|
- set_inverse_instance record if record.is_a? ActiveRecord::Base
- end
- end
- end
-
def build(attributes = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| build(attr, &block) }
@@ -168,14 +107,6 @@ module ActiveRecord
end
end
- def create(attributes = {}, &block)
- _create_record(attributes, &block)
- end
-
- def create!(attributes = {}, &block)
- _create_record(attributes, true, &block)
- end
-
# Add +records+ to this association. Returns +self+ so method calls may
# be chained. Since << flattens its argument list and inserts each record,
# +push+ and +concat+ behave identically.
@@ -223,12 +154,12 @@ module ActiveRecord
end
dependent = if dependent
- dependent
- elsif options[:dependent] == :destroy
- :delete_all
- else
- options[:dependent]
- end
+ dependent
+ elsif options[:dependent] == :destroy
+ :delete_all
+ else
+ options[:dependent]
+ end
delete_or_nullify_all_records(dependent).tap do
reset
@@ -246,31 +177,6 @@ module ActiveRecord
end
end
- # Returns the number of records. If no arguments are given, it counts all
- # columns using SQL. If one argument is given, it counts only the passed
- # column using SQL. If a block is given, it counts the number of records
- # yielding a true value.
- def count(column_name = nil)
- return super if block_given?
- relation = scope
- if association_scope.distinct_value
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
- column_name ||= reflection.klass.primary_key
- relation = relation.distinct
- end
-
- value = relation.count(column_name)
-
- limit = options[:limit]
- offset = options[:offset]
-
- if limit || offset
- [ [value - offset.to_i, 0].max, limit.to_i ].min
- else
- value
- end
- end
-
# Removes +records+ from this association calling +before_remove+ and
# +after_remove+ callbacks.
#
@@ -280,11 +186,8 @@ module ActiveRecord
# +delete_records+. They are in any case removed from the collection.
def delete(*records)
return if records.empty?
- _options = records.extract_options!
- dependent = _options[:dependent] || options[:dependent]
-
records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
- delete_or_destroy(records, dependent)
+ delete_or_destroy(records, options[:dependent])
end
# Deletes the +records+ and removes them from this association calling
@@ -310,14 +213,10 @@ module ActiveRecord
# +count_records+, which is a method descendants have to provide.
def size
if !find_target? || loaded?
- if association_scope.distinct_value
- target.uniq.size
- else
- target.size
- end
- elsif !loaded? && !association_scope.group_values.empty?
+ target.size
+ elsif !association_scope.group_values.empty?
load_target.size
- elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
+ elsif !association_scope.distinct_value && target.is_a?(Array)
unsaved_records = target.select(&:new_record?)
unsaved_records.size + count_records
else
@@ -325,15 +224,6 @@ module ActiveRecord
end
end
- # Returns the size of the collection calling +size+ on the target.
- #
- # If the collection has been already loaded +length+ and +size+ are
- # equivalent. If not and you are going to need the records anyway this
- # method will take one less query. Otherwise +size+ is more efficient.
- def length
- load_target.size
- end
-
# Returns true if the collection is empty.
#
# If the collection has been loaded
@@ -350,36 +240,6 @@ module ActiveRecord
end
end
- # Returns true if the collections is not empty.
- # If block given, loads all records and checks for one or more matches.
- # Otherwise, equivalent to +!collection.empty?+.
- def any?
- if block_given?
- load_target.any? { |*block_args| yield(*block_args) }
- else
- !empty?
- end
- end
-
- # Returns true if the collection has more than 1 record.
- # If block given, loads all records and checks for two or more matches.
- # Otherwise, equivalent to +collection.size > 1+.
- def many?
- if block_given?
- load_target.many? { |*block_args| yield(*block_args) }
- else
- size > 1
- end
- end
-
- def distinct
- seen = {}
- load_target.find_all do |record|
- seen[record.id] = true unless seen.key?(record.id)
- end
- end
- alias uniq distinct
-
# Replace this collection with +other_array+. This will perform a diff
# and delete/add only records that have changed.
def replace(other_array)
@@ -429,19 +289,28 @@ module ActiveRecord
def replace_on_target(record, index, skip_callbacks)
callback(:before_add, record) unless skip_callbacks
- was_loaded = loaded?
- yield(record) if block_given?
+ begin
+ if index
+ record_was = target[index]
+ target[index] = record
+ else
+ target << record
+ end
+
+ set_inverse_instance(record)
- unless !was_loaded && loaded?
+ yield(record) if block_given?
+ rescue
if index
- @target[index] = record
+ target[index] = record_was
else
- @target << record
+ target.delete(record)
end
+
+ raise
end
callback(:after_add, record) unless skip_callbacks
- set_inverse_instance(record)
record
end
@@ -456,26 +325,29 @@ module ActiveRecord
owner.new_record? && !foreign_key_present?
end
- private
- def get_records
- return scope.to_a if skip_statement_cache?
-
- conn = klass.connection
- sc = reflection.association_scope_cache(conn, owner) do
- StatementCache.create(conn) { |params|
- as = AssociationScope.create { params.bind }
- target_scope.merge as.scope(self, conn)
- }
- end
-
- binds = AssociationScope.get_bind_values(owner, reflection.chain)
- sc.execute binds, klass, klass.connection
+ def find_from_target?
+ loaded? ||
+ owner.new_record? ||
+ target.any? { |record| record.new_record? || record.changed? }
end
+ private
+
def find_target
- records = get_records
- records.each { |record| set_inverse_instance(record) }
- records
+ return scope.to_a if skip_statement_cache?
+
+ conn = klass.connection
+ sc = reflection.association_scope_cache(conn, owner) do
+ StatementCache.create(conn) { |params|
+ as = AssociationScope.create { params.bind }
+ target_scope.merge as.scope(self, conn)
+ }
+ end
+
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
+ sc.execute(binds, klass, conn) do |record|
+ set_inverse_instance(record)
+ end
end
# We have some records loaded from the database (persisted) and some that are
@@ -495,7 +367,7 @@ module ActiveRecord
persisted.map! do |record|
if mem_record = memory.delete(record)
- ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
+ ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save).each do |name|
mem_record[name] = record[name]
end
@@ -555,8 +427,9 @@ module ActiveRecord
records.each { |record| callback(:after_remove, record) }
end
- # Delete the given records from the association, using one of the methods :destroy,
- # :delete_all or :nullify (or nil, in which case a default is used).
+ # Delete the given records from the association,
+ # using one of the methods +:destroy+, +:delete_all+
+ # or +:nullify+ (or +nil+, in which case a default is used).
def delete_records(records, method)
raise NotImplementedError
end
@@ -605,25 +478,6 @@ module ActiveRecord
owner.class.send(full_callback_name)
end
- # Should we deal with assoc.first or assoc.last by issuing an independent query to
- # the database, or by getting the target, and then taking the first/last item from that?
- #
- # If the args is just a non-empty options hash, go to the database.
- #
- # Otherwise, go to the database only if none of the following are true:
- # * target already loaded
- # * owner is new record
- # * target contains new or changed record(s)
- def fetch_first_nth_or_last_using_find?(args)
- if args.first.is_a?(Hash)
- true
- else
- !(loaded? ||
- owner.new_record? ||
- target.any? { |record| record.new_record? || record.changed? })
- end
- end
-
def include_in_memory?(record)
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
assoc = owner.association(reflection.through_reflection.name)
@@ -650,16 +504,6 @@ module ActiveRecord
load_target.select { |r| ids.include?(r.id.to_s) }
end
end
-
- # Fetches the first/last using SQL if possible, otherwise from the target array.
- def first_nth_or_last(type, *args)
- args.shift if args.first.is_a?(Hash) && args.first.empty?
-
- collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
- collection.send(type, *args).tap do |record|
- set_inverse_instance record if record.is_a? ActiveRecord::Base
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 5d1e7ffb73..0d84805b4d 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -28,8 +28,7 @@ module ActiveRecord
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
class CollectionProxy < Relation
- delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
- delegate :find_nth, to: :scope
+ delegate :exists?, :update_all, :arel, to: :scope
def initialize(klass, association) #:nodoc:
@association = association
@@ -54,6 +53,12 @@ module ActiveRecord
@association.loaded?
end
+ ##
+ # :method: select
+ #
+ # :call-seq:
+ # select(*fields, &block)
+ #
# Works in two ways.
#
# *First:* Specify a subset of fields to be selected from the result set.
@@ -101,15 +106,6 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- #
- # person.pets.select(:name) { |pet| pet.name =~ /oo/ }
- # # => [
- # # #<Pet id: 2, name: "Spook">,
- # # #<Pet id: 3, name: "Choo-Choo">
- # # ]
- def select(*fields, &block)
- @association.select(*fields, &block)
- end
# Finds an object in the collection responding to the +id+. Uses the same
# rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound
@@ -141,6 +137,12 @@ module ActiveRecord
@association.find(*args, &block)
end
+ ##
+ # :method: first
+ #
+ # :call-seq:
+ # first(limit = nil)
+ #
# Returns the first record, or the first +n+ records, from the collection.
# If the collection is empty, the first form returns +nil+, and the second
# form returns an empty array.
@@ -167,45 +169,63 @@ module ActiveRecord
# another_person_without.pets # => []
# another_person_without.pets.first # => nil
# another_person_without.pets.first(3) # => []
- def first(*args)
- @association.first(*args)
- end
+ ##
+ # :method: second
+ #
+ # :call-seq:
+ # second()
+ #
# Same as #first except returns only the second record.
- def second(*args)
- @association.second(*args)
- end
+ ##
+ # :method: third
+ #
+ # :call-seq:
+ # third()
+ #
# Same as #first except returns only the third record.
- def third(*args)
- @association.third(*args)
- end
+ ##
+ # :method: fourth
+ #
+ # :call-seq:
+ # fourth()
+ #
# Same as #first except returns only the fourth record.
- def fourth(*args)
- @association.fourth(*args)
- end
+ ##
+ # :method: fifth
+ #
+ # :call-seq:
+ # fifth()
+ #
# Same as #first except returns only the fifth record.
- def fifth(*args)
- @association.fifth(*args)
- end
+ ##
+ # :method: forty_two
+ #
+ # :call-seq:
+ # forty_two()
+ #
# Same as #first except returns only the forty second record.
# Also known as accessing "the reddit".
- def forty_two(*args)
- @association.forty_two(*args)
- end
+ ##
+ # :method: third_to_last
+ #
+ # :call-seq:
+ # third_to_last()
+ #
# Same as #first except returns only the third-to-last record.
- def third_to_last(*args)
- @association.third_to_last(*args)
- end
+ ##
+ # :method: second_to_last
+ #
+ # :call-seq:
+ # second_to_last()
+ #
# Same as #first except returns only the second-to-last record.
- def second_to_last(*args)
- @association.second_to_last(*args)
- end
# Returns the last record, or the last +n+ records, from the collection.
# If the collection is empty, the first form returns +nil+, and the second
@@ -233,8 +253,9 @@ module ActiveRecord
# another_person_without.pets # => []
# another_person_without.pets.last # => nil
# another_person_without.pets.last(3) # => []
- def last(*args)
- @association.last(*args)
+ def last(limit = nil)
+ load_target if find_from_target?
+ super
end
# Gives a record (or N records if a parameter is supplied) from the collection
@@ -262,8 +283,9 @@ module ActiveRecord
# another_person_without.pets # => []
# another_person_without.pets.take # => nil
# another_person_without.pets.take(2) # => []
- def take(n = nil)
- @association.take(n)
+ def take(limit = nil)
+ load_target if find_from_target?
+ super
end
# Returns a new object of the collection type that has been instantiated
@@ -696,6 +718,12 @@ module ActiveRecord
@association.destroy(*records)
end
+ ##
+ # :method: distinct
+ #
+ # :call-seq:
+ # distinct(value = true)
+ #
# Specifies whether the records should be unique or not.
#
# class Person < ActiveRecord::Base
@@ -710,11 +738,32 @@ module ActiveRecord
#
# person.pets.select(:name).distinct
# # => [#<Pet name: "Fancy-Fancy">]
- def distinct
- @association.distinct
+ #
+ # person.pets.select(:name).distinct.distinct(false)
+ # # => [
+ # # #<Pet name: "Fancy-Fancy">,
+ # # #<Pet name: "Fancy-Fancy">
+ # # ]
+
+ #--
+ def uniq
+ load_target.uniq
end
- alias uniq distinct
+ def calculate(operation, column_name)
+ null_scope? ? scope.calculate(operation, column_name) : super
+ end
+
+ def pluck(*column_names)
+ null_scope? ? scope.pluck(*column_names) : super
+ end
+
+ ##
+ # :method: count
+ #
+ # :call-seq:
+ # count(column_name = nil, &block)
+ #
# Count all records.
#
# class Person < ActiveRecord::Base
@@ -734,9 +783,6 @@ module ActiveRecord
# perform the count using Ruby.
#
# person.pets.count { |pet| pet.name.include?('-') } # => 2
- def count(column_name = nil, &block)
- @association.count(column_name, &block)
- end
# Returns the size of the collection. If the collection hasn't been loaded,
# it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
@@ -766,6 +812,12 @@ module ActiveRecord
@association.size
end
+ ##
+ # :method: length
+ #
+ # :call-seq:
+ # length()
+ #
# Returns the size of the collection calling +size+ on the target.
# If the collection has been already loaded, +length+ and +size+ are
# equivalent. If not and you are going to need the records anyway this
@@ -786,14 +838,11 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def length
- @association.length
- end
# Returns +true+ if the collection is empty. If the collection has been
# loaded it is equivalent
# to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
- # it is equivalent to <tt>collection.exists?</tt>. If the collection has
+ # it is equivalent to <tt>!collection.exists?</tt>. If the collection has
# not already been loaded and you are going to fetch the records anyway it
# is better to check <tt>collection.length.zero?</tt>.
#
@@ -812,6 +861,12 @@ module ActiveRecord
@association.empty?
end
+ ##
+ # :method: any?
+ #
+ # :call-seq:
+ # any?()
+ #
# Returns +true+ if the collection is not empty.
#
# class Person < ActiveRecord::Base
@@ -841,10 +896,13 @@ module ActiveRecord
# pet.group == 'dogs'
# end
# # => true
- def any?(&block)
- @association.any?(&block)
- end
+ ##
+ # :method: many?
+ #
+ # :call-seq:
+ # many?()
+ #
# Returns true if the collection has more than one record.
# Equivalent to <tt>collection.size > 1</tt>.
#
@@ -879,9 +937,6 @@ module ActiveRecord
# pet.group == 'cats'
# end
# # => true
- def many?(&block)
- @association.many?(&block)
- end
# Returns +true+ if the given +record+ is present in the collection.
#
@@ -897,10 +952,6 @@ module ActiveRecord
!!@association.include?(record)
end
- def arel #:nodoc:
- scope.arel
- end
-
def proxy_association
@association
end
@@ -1074,6 +1125,30 @@ module ActiveRecord
proxy_association.reset_scope
self
end
+
+ private
+
+ def find_nth_with_limit(index, limit)
+ load_target if find_from_target?
+ super
+ end
+
+ def find_nth_from_last(index)
+ load_target if find_from_target?
+ super
+ end
+
+ def null_scope?
+ @association.null_scope?
+ end
+
+ def find_from_target?
+ @association.find_from_target?
+ end
+
+ def exec_queries
+ load_target
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index a9f6aaafef..25613d2fa7 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -16,36 +16,26 @@ module ActiveRecord
when :restrict_with_error
unless empty?
record = owner.class.human_attribute_name(reflection.name).downcase
- message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.many', record: record, raise: true) rescue nil
- if message
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- The error key `:'restrict_dependent_destroy.many'` has been deprecated and will be removed in Rails 5.1.
- Please use `:'restrict_dependent_destroy.has_many'` instead.
- MESSAGE
- end
- owner.errors.add(:base, message || :'restrict_dependent_destroy.has_many', record: record)
+ owner.errors.add(:base, :'restrict_dependent_destroy.has_many', record: record)
throw(:abort)
end
+ when :destroy
+ # No point in executing the counter update since we're going to destroy the parent anyway
+ load_target.each { |t| t.destroyed_by_association = reflection }
+ destroy_all
else
- if options[:dependent] == :destroy
- # No point in executing the counter update since we're going to destroy the parent anyway
- load_target.each { |t| t.destroyed_by_association = reflection }
- destroy_all
- else
- delete_all
- end
+ delete_all
end
end
def insert_record(record, validate = true, raise = false)
set_owner_attributes(record)
- set_inverse_instance(record)
if raise
- record.save!(:validate => validate)
+ record.save!(validate: validate)
else
- record.save(:validate => validate)
+ record.save(validate: validate)
end
end
@@ -74,7 +64,7 @@ module ActiveRecord
# the loaded flag is set to true as well.
def count_records
count = if reflection.has_cached_counter?
- owner._read_attribute reflection.counter_cache_column
+ owner._read_attribute(reflection.counter_cache_column).to_i
else
scope.count
end
@@ -82,7 +72,7 @@ module ActiveRecord
# If there's nothing in the database and @target has no new records
# we are certain the current target is an empty array. This is a
# documented side-effect of the method that may avoid an extra SELECT.
- @target ||= [] and loaded! if count == 0
+ (@target ||= []) && loaded! if count == 0
[association_scope.limit_value, count].compact.min
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 36fc381343..c4a7fe4432 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -38,10 +38,12 @@ module ActiveRecord
def insert_record(record, validate = true, raise = false)
ensure_not_nested
- if raise
- record.save!(:validate => validate)
- else
- return unless record.save(:validate => validate)
+ if record.new_record? || record.has_changes_to_save?
+ if raise
+ record.save!(validate: validate)
+ else
+ return unless record.save(validate: validate)
+ end
end
save_through_record(record)
@@ -86,7 +88,10 @@ module ActiveRecord
end
def save_through_record(record)
- build_through_record(record).save!
+ association = build_through_record(record)
+ if association.changed?
+ association.save!
+ end
ensure
@through_records.delete(record.object_id)
end
@@ -146,7 +151,7 @@ module ActiveRecord
stmt.from scope.klass.arel_table
stmt.wheres = arel.constraints
- count = scope.klass.connection.delete(stmt, 'SQL', scope.bound_attributes)
+ count = scope.klass.connection.delete(stmt, "SQL", scope.bound_attributes)
end
when :nullify
count = scope.update_all(source_reflection.foreign_key => nil)
@@ -196,7 +201,7 @@ module ActiveRecord
def find_target
return [] unless target_reflection_has_associated_record?
- get_records
+ super
end
# NOTE - not sure that we can actually cope with inverses here
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 0fe9b2e81b..b624154def 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -12,14 +12,7 @@ module ActiveRecord
when :restrict_with_error
if load_target
record = owner.class.human_attribute_name(reflection.name).downcase
- message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.one', record: record, raise: true) rescue nil
- if message
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- The error key `:'restrict_dependent_destroy.one'` has been deprecated and will be removed in Rails 5.1.
- Please use `:'restrict_dependent_destroy.has_one'` instead.
- MESSAGE
- end
- owner.errors.add(:base, message || :'restrict_dependent_destroy.has_one', record: record)
+ owner.errors.add(:base, :'restrict_dependent_destroy.has_one', record: record)
throw(:abort)
end
@@ -32,10 +25,10 @@ module ActiveRecord
raise_on_type_mismatch!(record) if record
load_target
- return self.target if !(target || record)
+ return target unless target || record
assigning_another_record = target != record
- if assigning_another_record || record.changed?
+ if assigning_another_record || record.has_changes_to_save?
save &&= owner.persisted?
transaction_if(save) do
@@ -60,12 +53,12 @@ module ActiveRecord
def delete(method = options[:dependent])
if load_target
case method
- when :delete
- target.delete
- when :destroy
- target.destroy
- when :nullify
- target.update_columns(reflection.foreign_key => nil) if target.persisted?
+ when :delete
+ target.delete
+ when :destroy
+ target.destroy
+ when :nullify
+ target.update_columns(reflection.foreign_key => nil) if target.persisted?
end
end
end
@@ -82,18 +75,19 @@ module ActiveRecord
def remove_target!(method)
case method
- when :delete
- target.delete
- when :destroy
- target.destroy
- else
- nullify_owner_attributes(target)
-
- if target.persisted? && owner.persisted? && !target.save
- set_owner_attributes(target)
- raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
- "The record failed to save after its foreign key was set to nil."
- end
+ when :delete
+ target.delete
+ when :destroy
+ target.destroy
+ else
+ nullify_owner_attributes(target)
+ remove_inverse_instance(target)
+
+ if target.persisted? && owner.persisted? && !target.save
+ set_owner_attributes(target)
+ raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
+ "The record failed to save after its foreign key was set to nil."
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index 08e0ec691f..604904abcc 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -15,7 +15,7 @@ module ActiveRecord
ensure_not_nested
through_proxy = owner.association(through_reflection.name)
- through_record = through_proxy.send(:load_target)
+ through_record = through_proxy.load_target
if through_record && !record
through_record.destroy
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 491152bbcb..4cd1e64c3d 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -1,18 +1,18 @@
module ActiveRecord
module Associations
class JoinDependency # :nodoc:
- autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
- autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
+ autoload :JoinBase, "active_record/associations/join_dependency/join_base"
+ autoload :JoinAssociation, "active_record/associations/join_dependency/join_association"
class Aliases # :nodoc:
def initialize(tables)
@tables = tables
- @alias_cache = tables.each_with_object({}) { |table,h|
- h[table.node] = table.columns.each_with_object({}) { |column,i|
+ @alias_cache = tables.each_with_object({}) { |table, h|
+ h[table.node] = table.columns.each_with_object({}) { |column, i|
i[column.name] = column.alias
}
}
- @name_and_alias_cache = tables.each_with_object({}) { |table,h|
+ @name_and_alias_cache = tables.each_with_object({}) { |table, h|
h[table.node] = table.columns.map { |column|
[column.name, column.alias]
}
@@ -62,7 +62,7 @@ module ActiveRecord
walk_tree assoc, hash
end
when Hash
- associations.each do |k,v|
+ associations.each do |k, v|
cache = hash[k] ||= {}
walk_tree v, cache
end
@@ -126,8 +126,8 @@ module ActiveRecord
end
def aliases
- Aliases.new join_root.each_with_index.map { |join_part,i|
- columns = join_part.column_names.each_with_index.map { |column_name,j|
+ Aliases.new join_root.each_with_index.map { |join_part, i|
+ columns = join_part.column_names.each_with_index.map { |column_name, j|
Aliases::Column.new column_name, "t#{i}_r#{j}"
}
Aliases::Table.new(join_part, columns)
@@ -143,7 +143,7 @@ module ActiveRecord
}
}
- model_cache = Hash.new { |h,klass| h[klass] = {} }
+ model_cache = Hash.new { |h, klass| h[klass] = {} }
parents = model_cache[join_root]
column_aliases = aliases.column_aliases join_root
@@ -154,7 +154,7 @@ module ActiveRecord
class_name: join_root.base_klass.name
}
- message_bus.instrument('instantiation.active_record', payload) do
+ message_bus.instrument("instantiation.active_record", payload) do
result_set.each { |row_hash|
parent_key = primary_key ? row_hash[primary_key] : row_hash
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
@@ -167,134 +167,140 @@ module ActiveRecord
private
- def make_constraints(parent, child, tables, join_type)
- chain = child.reflection.chain
- foreign_table = parent.table
- foreign_klass = parent.base_klass
- child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain)
- end
+ def make_constraints(parent, child, tables, join_type)
+ chain = child.reflection.chain
+ foreign_table = parent.table
+ foreign_klass = parent.base_klass
+ child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain)
+ end
- def make_outer_joins(parent, child)
- tables = table_aliases_for(parent, child)
- join_type = Arel::Nodes::OuterJoin
- info = make_constraints parent, child, tables, join_type
+ def make_outer_joins(parent, child)
+ tables = table_aliases_for(parent, child)
+ join_type = Arel::Nodes::OuterJoin
+ info = make_constraints parent, child, tables, join_type
- [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
- end
+ [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
+ end
- def make_left_outer_joins(parent, child)
- tables = child.tables
- join_type = Arel::Nodes::OuterJoin
- info = make_constraints parent, child, tables, join_type
+ def make_left_outer_joins(parent, child)
+ tables = child.tables
+ join_type = Arel::Nodes::OuterJoin
+ info = make_constraints parent, child, tables, join_type
- [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) }
- end
-
- def make_inner_joins(parent, child)
- tables = child.tables
- join_type = Arel::Nodes::InnerJoin
- info = make_constraints parent, child, tables, join_type
+ [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) }
+ end
- [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
- end
+ def make_inner_joins(parent, child)
+ tables = child.tables
+ join_type = Arel::Nodes::InnerJoin
+ info = make_constraints parent, child, tables, join_type
- def table_aliases_for(parent, node)
- node.reflection.chain.map { |reflection|
- alias_tracker.aliased_table_for(
- reflection.table_name,
- table_alias_for(reflection, parent, reflection != node.reflection)
- )
- }
- end
+ [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
+ end
- def construct_tables!(parent, node)
- node.tables = table_aliases_for(parent, node)
- node.children.each { |child| construct_tables! node, child }
- end
+ def table_aliases_for(parent, node)
+ node.reflection.chain.map { |reflection|
+ alias_tracker.aliased_table_for(
+ reflection.table_name,
+ table_alias_for(reflection, parent, reflection != node.reflection)
+ )
+ }
+ end
- def table_alias_for(reflection, parent, join)
- name = "#{reflection.plural_name}_#{parent.table_name}"
- name << "_join" if join
- name
- end
+ def construct_tables!(parent, node)
+ node.tables = table_aliases_for(parent, node)
+ node.children.each { |child| construct_tables! node, child }
+ end
- def walk(left, right)
- intersection, missing = right.children.map { |node1|
- [left.children.find { |node2| node1.match? node2 }, node1]
- }.partition(&:first)
+ def table_alias_for(reflection, parent, join)
+ name = "#{reflection.plural_name}_#{parent.table_name}"
+ name << "_join" if join
+ name
+ end
- ojs = missing.flat_map { |_,n| make_outer_joins left, n }
- intersection.flat_map { |l,r| walk l, r }.concat ojs
- end
+ def walk(left, right)
+ intersection, missing = right.children.map { |node1|
+ [left.children.find { |node2| node1.match? node2 }, node1]
+ }.partition(&:first)
- def find_reflection(klass, name)
- klass._reflect_on_association(name) or
- raise ConfigurationError, "Can't join '#{ klass.name }' to association named '#{ name }'; perhaps you misspelled it?"
- end
+ ojs = missing.flat_map { |_, n| make_outer_joins left, n }
+ intersection.flat_map { |l, r| walk l, r }.concat ojs
+ end
- def build(associations, base_klass)
- associations.map do |name, right|
- reflection = find_reflection base_klass, name
- reflection.check_validity!
- reflection.check_eager_loadable!
+ def find_reflection(klass, name)
+ klass._reflect_on_association(name) ||
+ raise(ConfigurationError, "Can't join '#{klass.name}' to association named '#{name}'; perhaps you misspelled it?")
+ end
- if reflection.polymorphic?
- next unless @eager_loading
- raise EagerLoadPolymorphicError.new(reflection)
- end
+ def build(associations, base_klass)
+ associations.map do |name, right|
+ reflection = find_reflection base_klass, name
+ reflection.check_validity!
+ reflection.check_eager_loadable!
- JoinAssociation.new reflection, build(right, reflection.klass)
- end.compact
- end
+ if reflection.polymorphic?
+ next unless @eager_loading
+ raise EagerLoadPolymorphicError.new(reflection)
+ end
- def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
- return if ar_parent.nil?
+ JoinAssociation.new reflection, build(right, reflection.klass)
+ end.compact
+ end
- parent.children.each do |node|
- if node.reflection.collection?
- other = ar_parent.association(node.reflection.name)
- other.loaded!
- elsif ar_parent.association_cached?(node.reflection.name)
- model = ar_parent.association(node.reflection.name).target
- construct(model, node, row, rs, seen, model_cache, aliases)
- next
+ def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
+ return if ar_parent.nil?
+
+ parent.children.each do |node|
+ if node.reflection.collection?
+ other = ar_parent.association(node.reflection.name)
+ other.loaded!
+ elsif ar_parent.association_cached?(node.reflection.name)
+ model = ar_parent.association(node.reflection.name).target
+ construct(model, node, row, rs, seen, model_cache, aliases)
+ next
+ end
+
+ key = aliases.column_alias(node, node.primary_key)
+ id = row[key]
+ if id.nil?
+ nil_association = ar_parent.association(node.reflection.name)
+ nil_association.loaded!
+ next
+ end
+
+ model = seen[ar_parent.object_id][node.base_klass][id]
+
+ if model
+ construct(model, node, row, rs, seen, model_cache, aliases)
+ else
+ model = construct_model(ar_parent, node, row, model_cache, id, aliases)
+
+ if node.reflection.scope_for(node.base_klass).readonly_value
+ model.readonly!
+ end
+
+ seen[ar_parent.object_id][node.base_klass][id] = model
+ construct(model, node, row, rs, seen, model_cache, aliases)
+ end
end
+ end
- key = aliases.column_alias(node, node.primary_key)
- id = row[key]
- if id.nil?
- nil_association = ar_parent.association(node.reflection.name)
- nil_association.loaded!
- next
- end
+ def construct_model(record, node, row, model_cache, id, aliases)
+ other = record.association(node.reflection.name)
- model = seen[ar_parent.object_id][node.base_klass][id]
+ model = model_cache[node][id] ||=
+ node.instantiate(row, aliases.column_aliases(node)) do |m|
+ other.set_inverse_instance(m)
+ end
- if model
- construct(model, node, row, rs, seen, model_cache, aliases)
+ if node.reflection.collection?
+ other.target.push(model)
else
- model = construct_model(ar_parent, node, row, model_cache, id, aliases)
- model.readonly!
- seen[ar_parent.object_id][node.base_klass][id] = model
- construct(model, node, row, rs, seen, model_cache, aliases)
+ other.target = model
end
- end
- end
- def construct_model(record, node, row, model_cache, id, aliases)
- model = model_cache[node][id] ||= node.instantiate(row,
- aliases.column_aliases(node))
- other = record.association(node.reflection.name)
-
- if node.reflection.collection?
- other.target.push(model)
- else
- other.target = model
+ model
end
-
- other.set_inverse_instance(model)
- model
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index c5fbe0d1d1..a5705951f3 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -1,4 +1,4 @@
-require 'active_record/associations/join_dependency/join_part'
+require "active_record/associations/join_dependency/join_part"
module ActiveRecord
module Associations
@@ -56,7 +56,9 @@ module ActiveRecord
klass_scope =
if klass.current_scope
- klass.current_scope.clone
+ klass.current_scope.clone.tap { |scope|
+ scope.joins_values = []
+ }
else
relation = ActiveRecord::Relation.create(
klass,
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
index 3a26c25737..fca20514d1 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
@@ -1,4 +1,4 @@
-require 'active_record/associations/join_dependency/join_part'
+require "active_record/associations/join_dependency/join_part"
module ActiveRecord
module Associations
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
index 9c6573f913..61cec5403a 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
@@ -15,7 +15,7 @@ module ActiveRecord
# association.
attr_reader :base_klass, :children
- delegate :table_name, :column_names, :primary_key, :to => :base_klass
+ delegate :table_name, :column_names, :primary_key, to: :base_klass
def initialize(base_klass, children)
@base_klass = base_klass
@@ -62,8 +62,8 @@ module ActiveRecord
hash
end
- def instantiate(row, aliases)
- base_klass.instantiate(extract_record(row, aliases))
+ def instantiate(row, aliases, &block)
+ base_klass.instantiate(extract_record(row, aliases), &block)
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index e64af84e1a..9f77f38b35 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -42,16 +42,16 @@ module ActiveRecord
extend ActiveSupport::Autoload
eager_autoload do
- autoload :Association, 'active_record/associations/preloader/association'
- autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
- autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
- autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
-
- autoload :HasMany, 'active_record/associations/preloader/has_many'
- autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
- autoload :HasOne, 'active_record/associations/preloader/has_one'
- autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
- autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
+ autoload :Association, "active_record/associations/preloader/association"
+ autoload :SingularAssociation, "active_record/associations/preloader/singular_association"
+ autoload :CollectionAssociation, "active_record/associations/preloader/collection_association"
+ autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
+
+ autoload :HasMany, "active_record/associations/preloader/has_many"
+ autoload :HasManyThrough, "active_record/associations/preloader/has_many_through"
+ autoload :HasOne, "active_record/associations/preloader/has_one"
+ autoload :HasOneThrough, "active_record/associations/preloader/has_one_through"
+ autoload :BelongsTo, "active_record/associations/preloader/belongs_to"
end
NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
@@ -106,108 +106,108 @@ module ActiveRecord
private
- # Loads all the given data into +records+ for the +association+.
- def preloaders_on(association, records, scope)
- case association
- when Hash
- preloaders_for_hash(association, records, scope)
- when Symbol
- preloaders_for_one(association, records, scope)
- when String
- preloaders_for_one(association.to_sym, records, scope)
- else
- raise ArgumentError, "#{association.inspect} was not recognized for preload"
+ # Loads all the given data into +records+ for the +association+.
+ def preloaders_on(association, records, scope)
+ case association
+ when Hash
+ preloaders_for_hash(association, records, scope)
+ when Symbol
+ preloaders_for_one(association, records, scope)
+ when String
+ preloaders_for_one(association.to_sym, records, scope)
+ else
+ raise ArgumentError, "#{association.inspect} was not recognized for preload"
+ end
end
- end
- def preloaders_for_hash(association, records, scope)
- association.flat_map { |parent, child|
- loaders = preloaders_for_one parent, records, scope
+ def preloaders_for_hash(association, records, scope)
+ association.flat_map { |parent, child|
+ loaders = preloaders_for_one parent, records, scope
- recs = loaders.flat_map(&:preloaded_records).uniq
- loaders.concat Array.wrap(child).flat_map { |assoc|
- preloaders_on assoc, recs, scope
+ recs = loaders.flat_map(&:preloaded_records).uniq
+ loaders.concat Array.wrap(child).flat_map { |assoc|
+ preloaders_on assoc, recs, scope
+ }
+ loaders
}
- loaders
- }
- end
+ end
- # Loads all the given data into +records+ for a singular +association+.
- #
- # Functions by instantiating a preloader class such as Preloader::HasManyThrough and
- # call the +run+ method for each passed in class in the +records+ argument.
- #
- # Not all records have the same class, so group then preload group on the reflection
- # itself so that if various subclass share the same association then we do not split
- # them unnecessarily
- #
- # Additionally, polymorphic belongs_to associations can have multiple associated
- # classes, depending on the polymorphic_type field. So we group by the classes as
- # well.
- def preloaders_for_one(association, records, scope)
- grouped_records(association, records).flat_map do |reflection, klasses|
- klasses.map do |rhs_klass, rs|
- loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
- loader.run self
- loader
+ # Loads all the given data into +records+ for a singular +association+.
+ #
+ # Functions by instantiating a preloader class such as Preloader::HasManyThrough and
+ # call the +run+ method for each passed in class in the +records+ argument.
+ #
+ # Not all records have the same class, so group then preload group on the reflection
+ # itself so that if various subclass share the same association then we do not split
+ # them unnecessarily
+ #
+ # Additionally, polymorphic belongs_to associations can have multiple associated
+ # classes, depending on the polymorphic_type field. So we group by the classes as
+ # well.
+ def preloaders_for_one(association, records, scope)
+ grouped_records(association, records).flat_map do |reflection, klasses|
+ klasses.map do |rhs_klass, rs|
+ loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
+ loader.run self
+ loader
+ end
end
end
- end
- def grouped_records(association, records)
- h = {}
- records.each do |record|
- next unless record
- assoc = record.association(association)
- klasses = h[assoc.reflection] ||= {}
- (klasses[assoc.klass] ||= []) << record
+ def grouped_records(association, records)
+ h = {}
+ records.each do |record|
+ next unless record
+ assoc = record.association(association)
+ klasses = h[assoc.reflection] ||= {}
+ (klasses[assoc.klass] ||= []) << record
+ end
+ h
end
- h
- end
- class AlreadyLoaded # :nodoc:
- attr_reader :owners, :reflection
+ class AlreadyLoaded # :nodoc:
+ attr_reader :owners, :reflection
- def initialize(klass, owners, reflection, preload_scope)
- @owners = owners
- @reflection = reflection
- end
+ def initialize(klass, owners, reflection, preload_scope)
+ @owners = owners
+ @reflection = reflection
+ end
- def run(preloader); end
+ def run(preloader); end
- def preloaded_records
- owners.flat_map { |owner| owner.association(reflection.name).target }
+ def preloaded_records
+ owners.flat_map { |owner| owner.association(reflection.name).target }
+ end
end
- end
- class NullPreloader # :nodoc:
- def self.new(klass, owners, reflection, preload_scope); self; end
- def self.run(preloader); end
- def self.preloaded_records; []; end
- def self.owners; []; end
- end
+ class NullPreloader # :nodoc:
+ def self.new(klass, owners, reflection, preload_scope); self; end
+ def self.run(preloader); end
+ def self.preloaded_records; []; end
+ def self.owners; []; end
+ end
- # Returns a class containing the logic needed to load preload the data
- # and attach it to a relation. For example +Preloader::Association+ or
- # +Preloader::HasManyThrough+. The class returned implements a `run` method
- # that accepts a preloader.
- def preloader_for(reflection, owners, rhs_klass)
- return NullPreloader unless rhs_klass
+ # Returns a class containing the logic needed to load preload the data
+ # and attach it to a relation. For example +Preloader::Association+ or
+ # +Preloader::HasManyThrough+. The class returned implements a `run` method
+ # that accepts a preloader.
+ def preloader_for(reflection, owners, rhs_klass)
+ return NullPreloader unless rhs_klass
- if owners.first.association(reflection.name).loaded?
- return AlreadyLoaded
- end
- reflection.check_preloadable!
-
- case reflection.macro
- when :has_many
- reflection.options[:through] ? HasManyThrough : HasMany
- when :has_one
- reflection.options[:through] ? HasOneThrough : HasOne
- when :belongs_to
- BelongsTo
+ if owners.first.association(reflection.name).loaded?
+ return AlreadyLoaded
+ end
+ reflection.check_preloadable!
+
+ case reflection.macro
+ when :has_many
+ reflection.options[:through] ? HasManyThrough : HasMany
+ when :has_one
+ reflection.options[:through] ? HasOneThrough : HasOne
+ when :belongs_to
+ BelongsTo
+ end
end
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 3032bc786e..4072d19380 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -28,10 +28,6 @@ module ActiveRecord
end
def records_for(ids)
- query_scope(ids)
- end
-
- def query_scope(ids)
scope.where(association_key_name => ids)
end
@@ -61,99 +57,113 @@ module ActiveRecord
private
- def associated_records_by_owner(preloader)
- records = load_records
- owners.each_with_object({}) do |owner, result|
- result[owner] = records[convert_key(owner[owner_key_name])] || []
+ def associated_records_by_owner(preloader)
+ records = load_records do |record|
+ owner = owners_by_key[convert_key(record[association_key_name])]
+ association = owner.association(reflection.name)
+ association.set_inverse_instance(record)
+ end
+
+ owners.each_with_object({}) do |owner, result|
+ result[owner] = records[convert_key(owner[owner_key_name])] || []
+ end
end
- end
- def owner_keys
- unless defined?(@owner_keys)
- @owner_keys = owners.map do |owner|
- owner[owner_key_name]
+ def owner_keys
+ unless defined?(@owner_keys)
+ @owner_keys = owners.map do |owner|
+ owner[owner_key_name]
+ end
+ @owner_keys.uniq!
+ @owner_keys.compact!
end
- @owner_keys.uniq!
- @owner_keys.compact!
+ @owner_keys
end
- @owner_keys
- end
- def key_conversion_required?
- @key_conversion_required ||= association_key_type != owner_key_type
- end
+ def owners_by_key
+ unless defined?(@owners_by_key)
+ @owners_by_key = owners.each_with_object({}) do |owner, h|
+ h[convert_key(owner[owner_key_name])] = owner
+ end
+ end
+ @owners_by_key
+ end
- def convert_key(key)
- if key_conversion_required?
- key.to_s
- else
- key
+ def key_conversion_required?
+ @key_conversion_required ||= association_key_type != owner_key_type
end
- end
- def association_key_type
- @klass.type_for_attribute(association_key_name.to_s).type
- end
+ def convert_key(key)
+ if key_conversion_required?
+ key.to_s
+ else
+ key
+ end
+ end
- def owner_key_type
- @model.type_for_attribute(owner_key_name.to_s).type
- end
+ def association_key_type
+ @klass.type_for_attribute(association_key_name.to_s).type
+ end
+
+ def owner_key_type
+ @model.type_for_attribute(owner_key_name.to_s).type
+ end
- def load_records
- return {} if owner_keys.empty?
- # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
- # Make several smaller queries if necessary or make one query if the adapter supports it
- slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
- @preloaded_records = slices.flat_map do |slice|
- records_for(slice)
+ def load_records(&block)
+ return {} if owner_keys.empty?
+ # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
+ # Make several smaller queries if necessary or make one query if the adapter supports it
+ slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
+ @preloaded_records = slices.flat_map do |slice|
+ records_for(slice).load(&block)
+ end
+ @preloaded_records.group_by do |record|
+ convert_key(record[association_key_name])
+ end
end
- @preloaded_records.group_by do |record|
- convert_key(record[association_key_name])
+
+ def reflection_scope
+ @reflection_scope ||= reflection.scope_for(klass)
end
- end
- def reflection_scope
- @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
- end
+ def build_scope
+ scope = klass.unscoped
- def build_scope
- scope = klass.unscoped
+ values = reflection_scope.values
+ preload_values = preload_scope.values
- values = reflection_scope.values
- preload_values = preload_scope.values
+ scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause
+ scope.references_values = Array(values[:references]) + Array(preload_values[:references])
- scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause
- scope.references_values = Array(values[:references]) + Array(preload_values[:references])
+ if preload_values[:select] || values[:select]
+ scope._select!(preload_values[:select] || values[:select])
+ end
+ scope.includes! preload_values[:includes] || values[:includes]
+ if preload_scope.joins_values.any?
+ scope.joins!(preload_scope.joins_values)
+ else
+ scope.joins!(reflection_scope.joins_values)
+ end
- if preload_values[:select] || values[:select]
- scope._select!(preload_values[:select] || values[:select])
- end
- scope.includes! preload_values[:includes] || values[:includes]
- if preload_scope.joins_values.any?
- scope.joins!(preload_scope.joins_values)
- else
- scope.joins!(reflection_scope.joins_values)
- end
+ if order_values = preload_values[:order] || values[:order]
+ scope.order!(order_values)
+ end
- if order_values = preload_values[:order] || values[:order]
- scope.order!(order_values)
- end
+ if preload_values[:reordering] || values[:reordering]
+ scope.reordering_value = true
+ end
- if preload_values[:reordering] || values[:reordering]
- scope.reordering_value = true
- end
+ if preload_values[:readonly] || values[:readonly]
+ scope.readonly!
+ end
- if preload_values[:readonly] || values[:readonly]
- scope.readonly!
- end
+ if options[:as]
+ scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
+ end
- if options[:as]
- scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
+ scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
+ klass.default_scoped.merge(scope)
end
-
- scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
- klass.default_scoped.merge(scope)
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/belongs_to.rb b/activerecord/lib/active_record/associations/preloader/belongs_to.rb
index 5091d4717a..38e231826c 100644
--- a/activerecord/lib/active_record/associations/preloader/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/preloader/belongs_to.rb
@@ -2,7 +2,6 @@ module ActiveRecord
module Associations
class Preloader
class BelongsTo < SingularAssociation #:nodoc:
-
def association_key_name
reflection.options[:primary_key] || klass && klass.primary_key
end
@@ -10,7 +9,6 @@ module ActiveRecord
def owner_key_name
reflection.foreign_key
end
-
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb
index 9939280fa4..26690bf16d 100644
--- a/activerecord/lib/active_record/associations/preloader/collection_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/collection_association.rb
@@ -4,14 +4,13 @@ module ActiveRecord
class CollectionAssociation < Association #:nodoc:
private
- def preload(preloader)
- associated_records_by_owner(preloader).each do |owner, records|
- association = owner.association(reflection.name)
- association.loaded!
- association.target.concat(records)
- records.each { |record| association.set_inverse_instance(record) }
+ def preload(preloader)
+ associated_records_by_owner(preloader).each do |owner, records|
+ association = owner.association(reflection.name)
+ association.loaded!
+ association.target.concat(records)
+ end
end
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_many.rb b/activerecord/lib/active_record/associations/preloader/has_many.rb
index 3ea91a8c11..20df1cc19a 100644
--- a/activerecord/lib/active_record/associations/preloader/has_many.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_many.rb
@@ -2,7 +2,6 @@ module ActiveRecord
module Associations
class Preloader
class HasMany < CollectionAssociation #:nodoc:
-
def association_key_name
reflection.foreign_key
end
@@ -10,7 +9,6 @@ module ActiveRecord
def owner_key_name
reflection.active_record_primary_key
end
-
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb
index f60647a81e..5c5828262e 100644
--- a/activerecord/lib/active_record/associations/preloader/singular_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/singular_association.rb
@@ -2,19 +2,16 @@ module ActiveRecord
module Associations
class Preloader
class SingularAssociation < Association #:nodoc:
-
private
- def preload(preloader)
- associated_records_by_owner(preloader).each do |owner, associated_records|
- record = associated_records.first
+ def preload(preloader)
+ associated_records_by_owner(preloader).each do |owner, associated_records|
+ record = associated_records.first
- association = owner.association(reflection.name)
- association.target = record
- association.set_inverse_instance(record) if record
+ association = owner.association(reflection.name)
+ association.target = record
+ end
end
- end
-
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index b0203909ce..9d44a02021 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -24,7 +24,7 @@ module ActiveRecord
reset_association owners, through_reflection.name
- middle_records = through_records.flat_map { |(_,rec)| rec }
+ middle_records = through_records.flat_map { |(_, rec)| rec }
preloaders = preloader.preload(middle_records,
source_reflection.name,
@@ -32,13 +32,13 @@ module ActiveRecord
@preloaded_records = preloaders.flat_map(&:preloaded_records)
- middle_to_pl = preloaders.each_with_object({}) do |pl,h|
+ middle_to_pl = preloaders.each_with_object({}) do |pl, h|
pl.owners.each { |middle|
h[middle] = pl
}
end
- through_records.each_with_object({}) do |(lhs,center), records_by_owner|
+ through_records.each_with_object({}) do |(lhs, center), records_by_owner|
pl_to_middle = center.group_by { |record| middle_to_pl[record] }
records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
@@ -61,48 +61,47 @@ module ActiveRecord
private
- def id_to_index_map(ids)
- id_map = {}
- ids.each_with_index { |id, index| id_map[id] = index }
- id_map
- end
+ def id_to_index_map(ids)
+ id_map = {}
+ ids.each_with_index { |id, index| id_map[id] = index }
+ id_map
+ end
- def reset_association(owners, association_name)
- should_reset = (through_scope != through_reflection.klass.unscoped) ||
- (reflection.options[:source_type] && through_reflection.collection?)
+ def reset_association(owners, association_name)
+ should_reset = (through_scope != through_reflection.klass.unscoped) ||
+ (reflection.options[:source_type] && through_reflection.collection?)
- # Don't cache the association - we would only be caching a subset
- if should_reset
- owners.each { |owner|
- owner.association(association_name).reset
- }
+ # Don't cache the association - we would only be caching a subset
+ if should_reset
+ owners.each { |owner|
+ owner.association(association_name).reset
+ }
+ end
end
- end
+ def through_scope
+ scope = through_reflection.klass.unscoped
- def through_scope
- scope = through_reflection.klass.unscoped
+ if options[:source_type]
+ scope.where! reflection.foreign_type => options[:source_type]
+ else
+ unless reflection_scope.where_clause.empty?
+ scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
+ scope.where_clause = reflection_scope.where_clause
+ end
- if options[:source_type]
- scope.where! reflection.foreign_type => options[:source_type]
- else
- unless reflection_scope.where_clause.empty?
- scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
- scope.where_clause = reflection_scope.where_clause
+ scope.references! reflection_scope.values[:references]
+ if scope.eager_loading? && order_values = reflection_scope.values[:order]
+ scope = scope.order(order_values)
+ end
end
- scope.references! reflection_scope.values[:references]
- if scope.eager_loading? && order_values = reflection_scope.values[:order]
- scope = scope.order(order_values)
- end
+ scope
end
- scope
- end
-
- def target_records_from_association(association)
- association.loaded? ? association.target : association.reader
- end
+ def target_records_from_association(association)
+ association.loaded? ? association.target : association.reader
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index f913f0852a..91580a28d0 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -2,16 +2,8 @@ module ActiveRecord
module Associations
class SingularAssociation < Association #:nodoc:
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
- def reader(force_reload = false)
- if force_reload && klass
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing an argument to force an association to reload is now
- deprecated and will be removed in Rails 5.1. Please call `reload`
- on the parent object instead.
- MSG
-
- klass.uncached { reload }
- elsif !loaded? || stale_target?
+ def reader
+ if !loaded? || stale_target?
reload
end
@@ -23,14 +15,6 @@ module ActiveRecord
replace(record)
end
- def create(attributes = {}, &block)
- _create_record(attributes, &block)
- end
-
- def create!(attributes = {}, &block)
- _create_record(attributes, true, &block)
- end
-
def build(attributes = {})
record = build_record(attributes)
yield(record) if block_given?
@@ -38,14 +22,21 @@ module ActiveRecord
record
end
+ # Implements the reload reader method, e.g. foo.reload_bar for
+ # Foo.has_one :bar
+ def force_reload_reader
+ klass.uncached { reload }
+ target
+ end
+
private
def create_scope
scope.scope_for_create.stringify_keys.except(klass.primary_key)
end
- def get_records
- return scope.limit(1).records if skip_statement_cache?
+ def find_target
+ return scope.take if skip_statement_cache?
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
@@ -56,13 +47,11 @@ module ActiveRecord
end
binds = AssociationScope.get_bind_values(owner, reflection.chain)
- sc.execute binds, klass, klass.connection
- end
-
- def find_target
- if record = get_records.first
+ sc.execute(binds, klass, conn) do |record|
set_inverse_instance record
- end
+ end.first
+ rescue ::RangeError
+ nil
end
def replace(record)
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index d0ec3e8015..6b87993ba3 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -2,10 +2,9 @@ module ActiveRecord
# = Active Record Through Association
module Associations
module ThroughAssociation #:nodoc:
+ delegate :source_reflection, :through_reflection, to: :reflection
- delegate :source_reflection, :through_reflection, :to => :reflection
-
- protected
+ private
# We merge in these scopes for two reasons:
#
@@ -22,8 +21,6 @@ module ActiveRecord
scope
end
- private
-
# Construct attributes for :through pointing to owner and associate. This is used by the
# methods which create and delete records on the association.
#