diff options
Diffstat (limited to 'activerecord/lib/active_record/associations.rb')
-rw-r--r-- | activerecord/lib/active_record/associations.rb | 428 |
1 files changed, 208 insertions, 220 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 371dad9104..a2db592c7d 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -20,18 +20,30 @@ module ActiveRecord end end - class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc: + class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc: def initialize(owner_class_name, reflection, source_reflection) super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.") end end + class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc: + def initialize(owner_class_name, reflection) + super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.") + end + end + class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc: def initialize(owner_class_name, reflection, source_reflection) super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.") end end + class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc: + def initialize(owner_class_name, reflection, through_reflection) + super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.") + end + end + class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc: def initialize(reflection) through_reflection = reflection.through_reflection @@ -107,7 +119,9 @@ module ActiveRecord # These classes will be loaded when associations are created. # So there is no need to eager load them. autoload :AssociationCollection, 'active_record/associations/association_collection' + autoload :SingularAssociation, 'active_record/associations/singular_association' autoload :AssociationProxy, 'active_record/associations/association_proxy' + autoload :ThroughAssociation, 'active_record/associations/through_association' autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association' autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association' autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association' @@ -118,30 +132,40 @@ module ActiveRecord # Clears out the association cache. def clear_association_cache #:nodoc: - self.class.reflect_on_all_associations.to_a.each do |assoc| - if IdentityMap.enabled? && instance_variable_defined?("@#{assoc.name}") - Array(instance_variable_get("@#{assoc.name}")).each do |t| - next unless t.respond_to?(:target) - IdentityMap.remove t.target unless t.target.nil? - end - end - instance_variable_set "@#{assoc.name}", nil - end if self.persisted? + @association_cache.clear if persisted? end + # :nodoc: + attr_reader :association_cache + + protected + + # Returns the proxy for the given association name, instantiating it if it doesn't + # already exist + def association_proxy(name) + association = association_instance_get(name) + + if association.nil? + reflection = self.class.reflect_on_association(name) + association = reflection.proxy_class.new(self, reflection) + association_instance_set(name, association) + end + + association + end + private # Returns the specified association instance if it responds to :loaded?, nil otherwise. def association_instance_get(name) - ivar = "@#{name}" - if instance_variable_defined?(ivar) - association = instance_variable_get(ivar) + if @association_cache.key? name + association = @association_cache[name] association if association.respond_to?(:loaded?) end end # Set the specified association instance. def association_instance_set(name, association) - instance_variable_set("@#{name}", association) + @association_cache[name] = association end # Associations are a set of macro-like class methods for tying objects together through @@ -185,7 +209,7 @@ module ActiveRecord # other=(other) | X | X | X # build_other(attributes={}) | X | | X # create_other(attributes={}) | X | | X - # other.create!(attributes={}) | | | X + # create_other!(attributes={}) | X | | X # # ===Collection associations (one-to-many / many-to-many) # | | | has_many @@ -208,10 +232,9 @@ module ActiveRecord # others.empty? | X | X | X # others.clear | X | X | X # others.delete(other,other,...) | X | X | X - # others.delete_all | X | X | + # others.delete_all | X | X | X # others.destroy_all | X | X | X # others.find(*args) | X | X | X - # others.find_first | X | | # others.exists? | X | X | X # others.uniq | X | X | X # others.reset | X | X | X @@ -325,26 +348,31 @@ module ActiveRecord # === One-to-one associations # # * Assigning an object to a +has_one+ association automatically saves that object and - # the object being replaced (if there is one), in order to update their primary - # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>). - # * If either of these saves fail (due to one of the objects being invalid) the assignment - # statement returns +false+ and the assignment is cancelled. + # the object being replaced (if there is one), in order to update their foreign + # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>). + # * If either of these saves fail (due to one of the objects being invalid), an + # <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is + # cancelled. # * If you wish to assign an object to a +has_one+ association without saving it, - # use the <tt>association.build</tt> method (documented below). + # use the <tt>build_association</tt> method (documented below). The object being + # replaced will still be saved to update its foreign key. # * Assigning an object to a +belongs_to+ association does not save the object, since - # the foreign key field belongs on the parent. It does not save the parent either. + # the foreign key field belongs on the parent. It does not save the parent either. # # === Collections # # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically - # saves that object, except if the parent object (the owner of the collection) is not yet - # stored in the database. + # saves that object, except if the parent object (the owner of the collection) is not yet + # stored in the database. # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar) - # fails, then <tt>push</tt> returns +false+. + # fails, then <tt>push</tt> returns +false+. + # * If saving fails while replacing the collection (via <tt>association=</tt>), an + # <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is + # cancelled. # * You can add an object to a collection without automatically saving it by using the - # <tt>collection.build</tt> method (documented below). + # <tt>collection.build</tt> method (documented below). # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically - # saved when the parent is saved. + # saved when the parent is saved. # # === Association callbacks # @@ -804,6 +832,73 @@ module ActiveRecord # * does not work with <tt>:polymorphic</tt> associations. # * for +belongs_to+ associations +has_many+ inverse associations are ignored. # + # == Deleting from associations + # + # === Dependent associations + # + # +has_many+, +has_one+ and +belongs_to+ associations support the <tt>:dependent</tt> option. + # This allows you to specify that associated records should be deleted when the owner is + # deleted. + # + # For example: + # + # class Author + # has_many :posts, :dependent => :destroy + # end + # Author.find(1).destroy # => Will destroy all of the author's posts, too + # + # The <tt>:dependent</tt> option can have different values which specify how the deletion + # is done. For more information, see the documentation for this option on the different + # specific association types. + # + # === Delete or destroy? + # + # +has_many+ and +has_and_belongs_to_many+ associations have the methods <tt>destroy</tt>, + # <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>. + # + # For +has_and_belongs_to_many+, <tt>delete</tt> and <tt>destroy</tt> are the same: they + # cause the records in the join table to be removed. + # + # For +has_many+, <tt>destroy</tt> will always call the <tt>destroy</tt> method of the + # record(s) being removed so that callbacks are run. However <tt>delete</tt> will either + # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or + # if no <tt>:dependent</tt> option is given, then it will follow the default strategy. + # The default strategy is <tt>:nullify</tt> (set the foreign keys to <tt>nil</tt>), except for + # +has_many+ <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete + # the join records, without running their callbacks). + # + # There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that + # it returns the association rather than the records which have been deleted. + # + # === What gets deleted? + # + # There is a potential pitfall here: +has_and_belongs_to_many+ and +has_many+ <tt>:through</tt> + # associations have records in join tables, as well as the associated records. So when we + # call one of these deletion methods, what exactly should be deleted? + # + # The answer is that it is assumed that deletion on an association is about removing the + # <i>link</i> between the owner and the associated object(s), rather than necessarily the + # associated objects themselves. So with +has_and_belongs_to_many+ and +has_many+ + # <tt>:through</tt>, the join records will be deleted, but the associated records won't. + # + # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by_name('food'))</tt> + # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself + # to be removed from the database. + # + # However, there are examples where this strategy doesn't make sense. For example, suppose + # a person has many projects, and each project has many tasks. If we deleted one of a person's + # tasks, we would probably not want the project to be deleted. In this scenario, the delete method + # won't actually work: it can only be used if the association on the join model is a + # +belongs_to+. In other situations you are expected to perform operations directly on + # either the associated records or the <tt>:through</tt> association. + # + # With a regular +has_many+ there is no distinction between the "associated records" + # and the "link", so there is only one choice for what gets deleted. + # + # With +has_and_belongs_to_many+ and +has_many+ <tt>:through</tt>, if you want to delete the + # associated records themselves, you can always do something along the lines of + # <tt>person.tasks.each(&:destroy)</tt>. + # # == Type safety with <tt>ActiveRecord::AssociationTypeMismatch</tt> # # If you attempt to assign an object to an association that doesn't match the inferred @@ -828,6 +923,10 @@ module ActiveRecord # Removes one or more objects from the collection by setting their foreign keys to +NULL+. # Objects will be in addition destroyed if they're associated with <tt>:dependent => :destroy</tt>, # and deleted if they're associated with <tt>:dependent => :delete_all</tt>. + # + # If the <tt>:through</tt> option is used, then the join records are deleted (rather than + # nullified) by default, but you can specify <tt>:dependent => :destroy</tt> or + # <tt>:dependent => :nullify</tt> to override this. # [collection=objects] # Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt> # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is @@ -883,7 +982,7 @@ module ActiveRecord # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>) # The declaration can also include an options hash to specialize the behavior of the association. # - # === Supported options + # === Options # [:class_name] # Specify the class name of the association. Use it only if that name can't be inferred # from the association name. So <tt>has_many :products</tt> will by default be linked @@ -911,7 +1010,9 @@ module ActiveRecord # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. If set to # <tt>:restrict</tt> this object cannot be deleted if it has any associated object. # - # *Warning:* This option is ignored when used with <tt>:through</tt> option. + # If using with the <tt>:through</tt> option, the association on the join model must be + # a +belongs_to+, and the records which get deleted are the join records, rather than + # the associated records. # # [:finder_sql] # Specify a complete SQL statement to fetch the association. This is a good way to go for complex @@ -991,12 +1092,7 @@ module ActiveRecord reflection = create_has_many_reflection(association_id, options, &extension) configure_dependency_for_has_many(reflection) add_association_callbacks(reflection.name, reflection.options) - - if options[:through] - collection_accessor_methods(reflection, HasManyThroughAssociation) - else - collection_accessor_methods(reflection, HasManyAssociation) - end + collection_accessor_methods(reflection) end # Specifies a one-to-one association with another class. This method should only be used @@ -1014,12 +1110,14 @@ module ActiveRecord # [build_association(attributes = {})] # Returns a new object of the associated type that has been instantiated # with +attributes+ and linked to this object through a foreign key, but has not - # yet been saved. <b>Note:</b> This ONLY works if an association already exists. - # It will NOT work if the association is +nil+. + # yet been saved. # [create_association(attributes = {})] # Returns a new object of the associated type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that # has already been saved (if it passed the validation). + # [create_association!(attributes = {})] + # Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt> + # if the record is invalid. # # (+association+ is replaced with the symbol passed as the first argument, so # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.) @@ -1031,6 +1129,7 @@ module ActiveRecord # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>) # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) + # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>) # # === Options # @@ -1108,14 +1207,12 @@ module ActiveRecord def has_one(association_id, options = {}) if options[:through] reflection = create_has_one_through_reflection(association_id, options) - association_accessor_methods(reflection, ActiveRecord::Associations::HasOneThroughAssociation) else reflection = create_has_one_reflection(association_id, options) - association_accessor_methods(reflection, HasOneAssociation) - association_constructor_method(:build, reflection, HasOneAssociation) - association_constructor_method(:create, reflection, HasOneAssociation) + association_constructor_methods(reflection) configure_dependency_for_has_one(reflection) end + association_accessor_methods(reflection) end # Specifies a one-to-one association with another class. This method should only be used @@ -1137,6 +1234,9 @@ module ActiveRecord # Returns a new object of the associated type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that # has already been saved (if it passed the validation). + # [create_association!(attributes = {})] + # Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt> + # if the record is invalid. # # (+association+ is replaced with the symbol passed as the first argument, so # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.) @@ -1148,6 +1248,7 @@ module ActiveRecord # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>) # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>) # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>) + # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>) # The declaration can also include an options hash to specialize the behavior of the association. # # === Options @@ -1169,6 +1270,11 @@ module ActiveRecord # association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly, # <tt>belongs_to :favorite_person, :class_name => "Person"</tt> will use a foreign key # of "favorite_person_id". + # [:foreign_type] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the association with a "_type" + # suffix. So a class that defines a <tt>belongs_to :taggable, :polymorphic => true</tt> + # association will use "taggable_type" as the default <tt>:foreign_type</tt>. # [:primary_key] # Specify the method that returns the primary key of associated object used for the association. # By default this is id. @@ -1227,16 +1333,12 @@ module ActiveRecord def belongs_to(association_id, options = {}) reflection = create_belongs_to_reflection(association_id, options) - if reflection.options[:polymorphic] - association_accessor_methods(reflection, BelongsToPolymorphicAssociation) - association_foreign_type_setter_method(reflection) - else - association_accessor_methods(reflection, BelongsToAssociation) - association_constructor_method(:build, reflection, BelongsToAssociation) - association_constructor_method(:create, reflection, BelongsToAssociation) + association_accessor_methods(reflection) + + unless reflection.options[:polymorphic] + association_constructor_methods(reflection) end - association_foreign_key_setter_method(reflection) add_counter_cache_callbacks(reflection) if options[:counter_cache] add_touch_callbacks(reflection, options[:touch]) if options[:touch] @@ -1271,12 +1373,6 @@ module ActiveRecord # end # end # - # Deprecated: Any additional fields added to the join table will be placed as attributes when - # pulling records out through +has_and_belongs_to_many+ associations. Records returned from join - # tables with additional attributes will be marked as readonly (because we can't save changes - # to the additional attributes). It's strongly recommended that you upgrade any - # associations with attributes to a real join model (see introduction). - # # Adds the following methods for retrieval and query: # # [collection(force_reload = false)] @@ -1419,15 +1515,15 @@ module ActiveRecord # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}' def has_and_belongs_to_many(association_id, options = {}, &extension) reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension) - collection_accessor_methods(reflection, HasAndBelongsToManyAssociation) + collection_accessor_methods(reflection) # Don't use a before_destroy callback since users' before_destroy # callbacks will be executed after the association is wiped out. include Module.new { class_eval <<-RUBY, __FILE__, __LINE__ + 1 def destroy # def destroy - #{reflection.name}.clear # posts.clear super # super + #{reflection.name}.clear # posts.clear end # end RUBY } @@ -1451,60 +1547,36 @@ module ActiveRecord table_name_prefix + join_table + table_name_suffix end - def association_accessor_methods(reflection, association_proxy_class) + def association_accessor_methods(reflection) redefine_method(reflection.name) do |*params| force_reload = params.first unless params.empty? - association = association_instance_get(reflection.name) - - if association.nil? || force_reload - association = association_proxy_class.new(self, reflection) - retval = force_reload ? reflection.klass.uncached { association.reload } : association.reload - if retval.nil? and association_proxy_class == BelongsToAssociation - association_instance_set(reflection.name, nil) - return nil - end - association_instance_set(reflection.name, association) - end - - association.target.nil? ? nil : association - end - - redefine_method("loaded_#{reflection.name}?") do - association = association_instance_get(reflection.name) - association && association.loaded? - end - - redefine_method("#{reflection.name}=") do |new_value| - association = association_instance_get(reflection.name) + association = association_proxy(reflection.name) - if association.nil? || association.target != new_value - association = association_proxy_class.new(self, reflection) + if force_reload + reflection.klass.uncached { association.reload } + elsif !association.loaded? || association.stale_target? + association.reload end - association.replace(new_value) - association_instance_set(reflection.name, new_value.nil? ? nil : association) + association.target.nil? ? nil : association end - redefine_method("set_#{reflection.name}_target") do |target| - return if target.nil? and association_proxy_class == BelongsToAssociation - association = association_proxy_class.new(self, reflection) - association.target = target - association_instance_set(reflection.name, association) + redefine_method("#{reflection.name}=") do |record| + association_proxy(reflection.name).replace(record) end end - def collection_reader_method(reflection, association_proxy_class) + def collection_reader_method(reflection) redefine_method(reflection.name) do |*params| force_reload = params.first unless params.empty? - association = association_instance_get(reflection.name) + association = association_proxy(reflection.name) - unless association - association = association_proxy_class.new(self, reflection) - association_instance_set(reflection.name, association) + if force_reload + reflection.klass.uncached { association.reload } + elsif association.stale_target? + association.reload end - reflection.klass.uncached { association.reload } if force_reload - association end @@ -1512,27 +1584,17 @@ module ActiveRecord if send(reflection.name).loaded? || reflection.options[:finder_sql] send(reflection.name).map { |r| r.id } else - if reflection.through_reflection && reflection.source_reflection.belongs_to? - through = reflection.through_reflection - primary_key = reflection.source_reflection.primary_key_name - send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map! { |r| r.send(primary_key) } - else - send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").except(:includes).map! { |r| r.id } - end + send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").except(:includes).map! { |r| r.id } end end - end - def collection_accessor_methods(reflection, association_proxy_class, writer = true) - collection_reader_method(reflection, association_proxy_class) + def collection_accessor_methods(reflection, writer = true) + collection_reader_method(reflection) if writer redefine_method("#{reflection.name}=") do |new_value| - # Loads proxy class instance (defined in collection_reader_method) if not already loaded - association = send(reflection.name) - association.replace(new_value) - association + association_proxy(reflection.name).replace(new_value) end redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| @@ -1544,62 +1606,19 @@ module ActiveRecord end end - def association_constructor_method(constructor, reflection, association_proxy_class) - redefine_method("#{constructor}_#{reflection.name}") do |*params| - attributees = params.first unless params.empty? - replace_existing = params[1].nil? ? true : params[1] - association = association_instance_get(reflection.name) - - unless association - association = association_proxy_class.new(self, reflection) - association_instance_set(reflection.name, association) - end - - if association_proxy_class == HasOneAssociation - association.send(constructor, attributees, replace_existing) - else - association.send(constructor, attributees) - end - end - end - - def association_foreign_key_setter_method(reflection) - setters = reflect_on_all_associations(:belongs_to).map do |belongs_to_reflection| - if belongs_to_reflection.primary_key_name == reflection.primary_key_name - "association_instance_set(:#{belongs_to_reflection.name}, nil);" + def association_constructor_methods(reflection) + constructors = { + "build_#{reflection.name}" => "build", + "create_#{reflection.name}" => "create", + "create_#{reflection.name}!" => "create!" + } + + constructors.each do |name, proxy_name| + redefine_method(name) do |*params| + attributes = params.first unless params.empty? + association_proxy(reflection.name).send(proxy_name, attributes) end - end.compact.join - - if method_defined?(:"#{reflection.primary_key_name}=") - undef_method :"#{reflection.primary_key_name}=" end - - class_eval <<-FILE, __FILE__, __LINE__ + 1 - def #{reflection.primary_key_name}=(new_id) - write_attribute :#{reflection.primary_key_name}, new_id - if #{reflection.primary_key_name}_changed? - #{ setters } - end - end - FILE - end - - def association_foreign_type_setter_method(reflection) - setters = reflect_on_all_associations(:belongs_to).map do |belongs_to_reflection| - if belongs_to_reflection.options[:foreign_type] == reflection.options[:foreign_type] - "association_instance_set(:#{belongs_to_reflection.name}, nil);" - end - end.compact.join - - field = reflection.options[:foreign_type] - class_eval <<-FILE, __FILE__, __LINE__ + 1 - def #{field}=(new_id) - write_attribute :#{field}, new_id - if #{field}_changed? - #{ setters } - end - end - FILE end def add_counter_cache_callbacks(reflection) @@ -1648,56 +1667,39 @@ module ActiveRecord # - set the foreign key to NULL if the option is set to :nullify # - do not delete the parent record if there is any child record if the # option is set to :restrict - # - # The +extra_conditions+ parameter, which is not used within the main - # Active Record codebase, is meant to allow plugins to define extra - # finder conditions. - def configure_dependency_for_has_many(reflection, extra_conditions = nil) - if reflection.options.include?(:dependent) + def configure_dependency_for_has_many(reflection) + if reflection.options[:dependent] + method_name = "has_many_dependent_for_#{reflection.name}" + case reflection.options[:dependent] - when :destroy - method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym - define_method(method_name) do + when :destroy, :delete_all, :nullify + define_method(method_name) do + if reflection.options[:dependent] == :destroy send(reflection.name).each do |o| # No point in executing the counter update since we're going to destroy the parent anyway counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym - if(o.respond_to? counter_method) then + if o.respond_to?(counter_method) class << o self end.send(:define_method, counter_method, Proc.new {}) end - o.destroy end end - before_destroy method_name - when :delete_all - before_destroy do |record| - self.class.send(:delete_all_has_many_dependencies, - record, - reflection.name, - reflection.klass, - reflection.dependent_conditions(record, self.class, extra_conditions)) - end - when :nullify - before_destroy do |record| - self.class.send(:nullify_has_many_dependencies, - record, - reflection.name, - reflection.klass, - reflection.primary_key_name, - reflection.dependent_conditions(record, self.class, extra_conditions)) - end - when :restrict - method_name = "has_many_dependent_restrict_for_#{reflection.name}".to_sym - define_method(method_name) do - unless send(reflection.name).empty? - raise DeleteRestrictionError.new(reflection) - end + + # AssociationProxy#delete_all looks at the :dependent option and acts accordingly + send(reflection.name).delete_all + end + when :restrict + define_method(method_name) do + unless send(reflection.name).empty? + raise DeleteRestrictionError.new(reflection) end - before_destroy method_name - else - raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, :nullify or :restrict (#{reflection.options[:dependent].inspect})" + end + else + raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, :nullify or :restrict (#{reflection.options[:dependent].inspect})" end + + before_destroy method_name end end @@ -1722,7 +1724,7 @@ module ActiveRecord class_eval <<-eoruby, __FILE__, __LINE__ + 1 def #{method_name} association = #{reflection.name} - association.update_attribute(#{reflection.primary_key_name.inspect}, nil) if association + association.update_attribute(#{reflection.foreign_key.inspect}, nil) if association end eoruby when :restrict @@ -1760,14 +1762,6 @@ module ActiveRecord end end - def delete_all_has_many_dependencies(record, reflection_name, association_class, dependent_conditions) - association_class.delete_all(dependent_conditions) - end - - def nullify_has_many_dependencies(record, reflection_name, association_class, primary_key_name, dependent_conditions) - association_class.update_all("#{primary_key_name} = NULL", dependent_conditions) - end - mattr_accessor :valid_keys_for_has_many_association @@valid_keys_for_has_many_association = [ :class_name, :table_name, :foreign_key, :primary_key, @@ -1816,13 +1810,7 @@ module ActiveRecord def create_belongs_to_reflection(association_id, options) options.assert_valid_keys(valid_keys_for_belongs_to_association) - reflection = create_reflection(:belongs_to, association_id, options, self) - - if options[:polymorphic] - reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type" - end - - reflection + create_reflection(:belongs_to, association_id, options, self) end mattr_accessor :valid_keys_for_has_and_belongs_to_many_association @@ -1842,7 +1830,7 @@ module ActiveRecord reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self) - if reflection.association_foreign_key == reflection.primary_key_name + if reflection.association_foreign_key == reflection.foreign_key raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection) end |