diff options
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.rb | 60 |
1 files changed, 38 insertions, 22 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 a74dd1cdab..35ad512537 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -12,17 +12,18 @@ 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. + # Returns the size of the collection by executing a SELECT COUNT(*) query + # if the collection hasn't been loaded, and by calling collection.size if + # it has. If the collection will likely have a size greater than zero, + # and if fetching the collection will be needed afterwards, one less + # SELECT query will be generated by using #length instead. def size if has_cached_counter? - owner.send(:read_attribute, cached_counter_attribute_name) + owner.read_attribute cached_counter_attribute_name(reflection) elsif loaded? target.size else - count + super end end @@ -30,7 +31,6 @@ module ActiveRecord unless owner.new_record? records.flatten.each do |record| raise_on_type_mismatch!(record) - record.save! if record.new_record? end end @@ -40,7 +40,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| @@ -73,23 +73,25 @@ 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 through_scope_attributes through_record.send("#{source_reflection.name}=", record) through_record end end + def through_scope_attributes + scope.where_values_hash(through_association.reflection.name.to_s) + end + def save_through_record(record) build_through_record(record).save! ensure @@ -128,19 +130,33 @@ 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) case method when :destroy - count = scope.destroy_all.length + if scope.klass.primary_key + count = scope.destroy_all.length + else + scope.to_a.each do |record| + record.run_callbacks :destroy + end + + arel = scope.arel + + stmt = Arel::DeleteManager.new arel.engine + stmt.from scope.klass.arel_table + stmt.wheres = arel.constraints + + count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values) + end when :nullify count = scope.update_all(source_reflection.foreign_key => nil) else @@ -149,7 +165,7 @@ module ActiveRecord delete_through_records(records) - if source_reflection.options[:counter_cache] + if source_reflection.options[:counter_cache] && method != :destroy counter = source_reflection.counter_cache_column klass.decrement_counter counter, records.map(&:id) end @@ -185,7 +201,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 |