aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/associations/has_many_through_association.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/associations/has_many_through_association.rb')
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb74
1 files changed, 34 insertions, 40 deletions
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 31b8d27892..f1e784d771 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
# = Active Record Has Many Through Association
module Associations
@@ -12,25 +11,10 @@ module ActiveRecord
@through_association = nil
end
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
- # loaded and calling collection.size if it has. If it's more likely than not that the collection does
- # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
- # SELECT query if you use #length.
- def size
- if has_cached_counter?
- owner.send(:read_attribute, cached_counter_attribute_name)
- elsif loaded?
- target.size
- else
- count
- end
- end
-
def concat(*records)
unless owner.new_record?
records.flatten.each do |record|
raise_on_type_mismatch!(record)
- record.save! if record.new_record?
end
end
@@ -40,7 +24,7 @@ module ActiveRecord
def concat_records(records)
ensure_not_nested
- records = super
+ records = super(records, true)
if owner.new_record? && records
records.flatten.each do |record|
@@ -63,7 +47,7 @@ module ActiveRecord
end
save_through_record(record)
- update_counter(1)
+
record
end
@@ -73,23 +57,31 @@ module ActiveRecord
@through_association ||= owner.association(through_reflection.name)
end
- # We temporarily cache through record that has been build, because if we build a
- # through record in build_record and then subsequently call insert_record, then we
- # want to use the exact same object.
+ # The through record (built with build_record) is temporarily cached
+ # so that it may be reused if insert_record is subsequently called.
#
- # However, after insert_record has been called, we clear the cache entry because
- # we want it to be possible to have multiple instances of the same record in an
- # association
+ # However, after insert_record has been called, the cache is cleared in
+ # order to allow multiple instances of the same record in an association.
def build_through_record(record)
@through_records[record.object_id] ||= begin
ensure_mutable
- through_record = through_association.build
+ through_record = through_association.build(*options_for_through_record)
through_record.send("#{source_reflection.name}=", record)
through_record
end
end
+ def options_for_through_record
+ [through_scope_attributes]
+ end
+
+ def through_scope_attributes
+ scope.where_values_hash(through_association.reflection.name.to_s).
+ except!(through_association.reflection.foreign_key,
+ through_association.reflection.klass.inheritance_column)
+ end
+
def save_through_record(record)
build_through_record(record).save!
ensure
@@ -103,9 +95,9 @@ module ActiveRecord
inverse = source_reflection.inverse_of
if inverse
- if inverse.macro == :has_many
+ if inverse.collection?
record.send(inverse.name) << build_through_record(record)
- elsif inverse.macro == :has_one
+ elsif inverse.has_one?
record.send("#{inverse.name}=", build_through_record(record))
end
end
@@ -114,7 +106,7 @@ module ActiveRecord
end
def target_reflection_has_associated_record?
- !(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?)
+ !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
end
def update_through_counter?(method)
@@ -128,13 +120,13 @@ module ActiveRecord
end
end
+ def delete_or_nullify_all_records(method)
+ delete_records(load_target, method)
+ end
+
def delete_records(records, method)
ensure_not_nested
- # This is unoptimised; it will load all the target records
- # even when we just want to delete everything.
- records = load_target if records == :all
-
scope = through_association.scope
scope.where! construct_join_attributes(*records)
@@ -143,13 +135,11 @@ module ActiveRecord
if scope.klass.primary_key
count = scope.destroy_all.length
else
- scope.to_a.each do |record|
- record.run_callbacks :destroy
- end
+ scope.each(&:_run_destroy_callbacks)
arel = scope.arel
- stmt = Arel::DeleteManager.new arel.engine
+ stmt = Arel::DeleteManager.new
stmt.from scope.klass.arel_table
stmt.wheres = arel.constraints
@@ -168,7 +158,7 @@ module ActiveRecord
klass.decrement_counter counter, records.map(&:id)
end
- if through_reflection.macro == :has_many && update_through_counter?(method)
+ if through_reflection.collection? && update_through_counter?(method)
update_counter(-count, through_reflection)
end
@@ -178,14 +168,18 @@ module ActiveRecord
def through_records_for(record)
attributes = construct_join_attributes(record)
candidates = Array.wrap(through_association.target)
- candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
+ candidates.find_all do |c|
+ attributes.all? do |key, value|
+ c.public_send(key) == value
+ end
+ end
end
def delete_through_records(records)
records.each do |record|
through_records = through_records_for(record)
- if through_reflection.macro == :has_many
+ if through_reflection.collection?
through_records.each { |r| through_association.target.delete(r) }
else
if through_records.include?(through_association.target)
@@ -199,7 +193,7 @@ module ActiveRecord
def find_target
return [] unless target_reflection_has_associated_record?
- scope.to_a
+ get_records
end
# NOTE - not sure that we can actually cope with inverses here