aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/associations/collection_association.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/associations/collection_association.rb')
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb91
1 files changed, 50 insertions, 41 deletions
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 62c944fce3..de8afc9ccb 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveRecord
module Associations
# = Active Record Association Collection
@@ -30,7 +32,8 @@ module ActiveRecord
reload
end
- CollectionProxy.create(klass, self)
+ @proxy ||= CollectionProxy.create(klass, self)
+ @proxy.reset_scope
end
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -43,23 +46,25 @@ module ActiveRecord
if loaded?
target.pluck(reflection.association_primary_key)
else
- @association_ids ||= (
- column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
- scope.pluck(column)
- )
+ @association_ids ||= scope.pluck(reflection.association_primary_key)
end
end
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
- pk_type = reflection.association_primary_key_type
+ primary_key = reflection.association_primary_key
+ pk_type = klass.type_for_attribute(primary_key.to_s)
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)
+
+ records = klass.where(primary_key => ids).index_by do |r|
+ r.public_send(primary_key)
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)
+ found_ids = records.map { |record| record.public_send(primary_key) }
+ not_found_ids = ids - found_ids
+ klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids)
else
replace(records)
end
@@ -68,26 +73,29 @@ module ActiveRecord
def reset
super
@target = []
+ @association_ids = nil
end
def find(*args)
- if block_given?
- load_target.find(*args) { |*block_args| yield(*block_args) }
- else
- if options[:inverse_of] && loaded?
- args_flatten = args.flatten
- raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
- result = find_by_scan(*args)
-
- result_size = Array(result).size
- if !result || result_size != args_flatten.size
- scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
- else
- result
- end
+ if options[:inverse_of] && loaded?
+ args_flatten = args.flatten
+ model = scope.klass
+
+ if args_flatten.blank?
+ error_message = "Couldn't find #{model.name} without an ID"
+ raise RecordNotFound.new(error_message, model.name, model.primary_key, args)
+ end
+
+ result = find_by_scan(*args)
+
+ result_size = Array(result).size
+ if !result || result_size != args_flatten.size
+ scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
else
- scope.find(*args)
+ result
end
+ else
+ scope.find(*args)
end
end
@@ -179,8 +187,6 @@ module ActiveRecord
# are actually removed from the database, that depends precisely on
# +delete_records+. They are in any case removed from the collection.
def delete(*records)
- return if records.empty?
- records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
delete_or_destroy(records, options[:dependent])
end
@@ -190,8 +196,6 @@ module ActiveRecord
# Note that this method removes records from the database ignoring the
# +:dependent+ option.
def destroy(*records)
- return if records.empty?
- records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
delete_or_destroy(records, :destroy)
end
@@ -299,18 +303,17 @@ module ActiveRecord
private
def find_target
- return scope.to_a if skip_statement_cache?
+ scope = self.scope
+ return scope.to_a if skip_statement_cache?(scope)
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)
- }
+ sc = reflection.association_scope_cache(conn, owner) do |params|
+ as = AssociationScope.create { params.bind }
+ target_scope.merge!(as.scope(self))
end
binds = AssociationScope.get_bind_values(owner, reflection.chain)
- sc.execute(binds, klass, conn) do |record|
+ sc.execute(binds, conn) do |record|
set_inverse_instance(record)
end
end
@@ -356,7 +359,10 @@ module ActiveRecord
transaction do
add_to_target(build_record(attributes)) do |record|
yield(record) if block_given?
- insert_record(record, true, raise) { @_was_loaded = loaded? }
+ insert_record(record, true, raise) {
+ @_was_loaded = loaded?
+ @association_ids = nil
+ }
end
end
end
@@ -371,11 +377,9 @@ module ActiveRecord
end
end
- def create_scope
- scope.scope_for_create.stringify_keys
- end
-
def delete_or_destroy(records, method)
+ return if records.empty?
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
records = records.flatten
records.each { |record| raise_on_type_mismatch!(record) }
existing_records = records.reject(&:new_record?)
@@ -429,7 +433,12 @@ module ActiveRecord
records.each do |record|
raise_on_type_mismatch!(record)
add_to_target(record) do
- result &&= insert_record(record, true, raise) { @_was_loaded = loaded? } unless owner.new_record?
+ unless owner.new_record?
+ result &&= insert_record(record, true, raise) {
+ @_was_loaded = loaded?
+ @association_ids = nil
+ }
+ end
end
end