diff options
author | Gonçalo Silva <goncalossilva@gmail.com> | 2011-03-24 17:21:17 +0000 |
---|---|---|
committer | Gonçalo Silva <goncalossilva@gmail.com> | 2011-03-24 17:21:17 +0000 |
commit | 9887f238871bb2dd73de6ce8855615bcc5d8d079 (patch) | |
tree | 74fa9ff9524a51701cfa23f708b3f777c65b7fe5 /activerecord/lib/active_record/associations/has_many_through_association.rb | |
parent | aff821508a16245ebc03510ba29c70379718dfb7 (diff) | |
parent | 5214e73850916de3c9127d35a4ecee0424d364a3 (diff) | |
download | rails-9887f238871bb2dd73de6ce8855615bcc5d8d079.tar.gz rails-9887f238871bb2dd73de6ce8855615bcc5d8d079.tar.bz2 rails-9887f238871bb2dd73de6ce8855615bcc5d8d079.zip |
Merge branch 'master' of https://github.com/rails/rails
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 | 166 |
1 files changed, 98 insertions, 68 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 608b1c741a..9d2b29685b 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -1,114 +1,144 @@ -require "active_record/associations/through_association_scope" require 'active_support/core_ext/object/blank' module ActiveRecord # = Active Record Has Many Through Association module Associations class HasManyThroughAssociation < HasManyAssociation #:nodoc: - include ThroughAssociationScope + include ThroughAssociation alias_method :new, :build - def create!(attrs = nil) - create_record(attrs, true) - end - - def create(attrs = nil) - create_record(attrs, false) + # 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 destroy(*records) - transaction do - delete_records(flatten_deeper(records)) - super + 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 + + super 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 - return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter? - return @target.size if loaded? - return count + def insert_record(record, validate = true) + ensure_not_nested + return if record.new_record? && !record.save(:validate => validate) + + through_record(record).save! + update_counter(1) + record end - protected - def create_record(attrs, force = true) - ensure_owner_is_not_new + private - transaction do - object = @reflection.klass.new(attrs) - add_record_to_target_with_callbacks(object) {|r| insert_record(object, force) } - object + def through_record(record) + through_association = owner.association(through_reflection.name) + attributes = construct_join_attributes(record) + + through_record = Array.wrap(through_association.target).find { |candidate| + candidate.attributes.slice(*attributes.keys) == attributes + } + + unless through_record + through_record = through_association.build(attributes) + through_record.send("#{source_reflection.name}=", record) + end + + through_record + end + + def build_record(attributes) + ensure_not_nested + + record = super(attributes) + + inverse = source_reflection.inverse_of + if inverse + if inverse.macro == :has_many + record.send(inverse.name) << through_record(record) + elsif inverse.macro == :has_one + record.send("#{inverse.name}=", through_record(record)) + end end + + record end def target_reflection_has_associated_record? - if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank? + if through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank? false else true end end - def construct_find_options!(options) - options[:joins] = construct_joins(options[:joins]) - options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include] + def update_through_counter?(method) + case method + when :destroy + !inverse_updates_counter_cache?(through_reflection) + when :nullify + false + else + true + end end - def insert_record(record, force = true, validate = true) - if record.new_record? - if force - record.save! - else - return false unless record.save(:validate => validate) - end - end + def delete_records(records, method) + ensure_not_nested - through_association = @owner.send(@reflection.through_reflection.name) - through_record = through_association.create!(construct_join_attributes(record)) - through_association.proxy_target << through_record - end + through = owner.association(through_reflection.name) + scope = through.scoped.where(construct_join_attributes(*records)) - # TODO - add dependent option support - def delete_records(records) - klass = @reflection.through_reflection.klass - records.each do |associate| - klass.delete_all(construct_join_attributes(associate)) + case method + when :destroy + count = scope.destroy_all.length + when :nullify + count = scope.update_all(source_reflection.foreign_key => nil) + else + count = scope.delete_all end - end - def find_target - return [] unless target_reflection_has_associated_record? - with_scope(construct_scope) { @reflection.klass.find(:all) } - end - - def construct_sql - case - when @reflection.options[:finder_sql] - @finder_sql = interpolate_sql(@reflection.options[:finder_sql]) + delete_through_records(through, records) - @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}" - @finder_sql << " AND (#{conditions})" if conditions - else - @finder_sql = construct_conditions + if through_reflection.macro == :has_many && update_through_counter?(method) + update_counter(-count, through_reflection) end - construct_counter_sql + update_counter(-count) end - def has_cached_counter? - @owner.attribute_present?(cached_counter_attribute_name) + def delete_through_records(through, records) + if through_reflection.macro == :has_many + records.each do |record| + through.target.delete(through_record(record)) + end + else + records.each do |record| + through.target = nil if through.target == through_record(record) + end + end end - def cached_counter_attribute_name - "#{@reflection.name}_count" + def find_target + return [] unless target_reflection_has_associated_record? + scoped.all end # NOTE - not sure that we can actually cope with inverses here - def we_can_set_the_inverse_on_this?(record) + def invertible_for?(record) false end end |