From ec8f04584479aff895b0b511a7ba1e9d33f84067 Mon Sep 17 00:00:00 2001 From: Eloy Duran Date: Sun, 1 Feb 2009 14:44:30 +1300 Subject: Add support for nested object forms to ActiveRecord and the helpers in ActionPack Signed-Off-By: Michael Koziarski [#1202 state:committed] --- activerecord/lib/active_record/associations.rb | 91 ++++++++++++++------------ 1 file changed, 48 insertions(+), 43 deletions(-) (limited to 'activerecord/lib/active_record/associations.rb') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 8b51a38f48..7a88465f8c 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -88,6 +88,18 @@ module ActiveRecord end unless self.new_record? end + private + # Gets the specified association instance if it responds to :loaded?, nil otherwise. + def association_instance_get(name) + association = instance_variable_get("@#{name}") + association if association.respond_to?(:loaded?) + end + + # Set the specified association instance. + def association_instance_set(name, association) + instance_variable_set("@#{name}", association) + end + # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own attr* @@ -256,6 +268,10 @@ module ActiveRecord # You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be # aware of, mostly involving the saving of associated objects. # + # Unless you enable the :autosave option on a has_one, belongs_to, + # has_many, or has_and_belongs_to_many association, + # in which case the members are always saved. + # # === 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 @@ -752,6 +768,9 @@ module ActiveRecord # If true, all the associated objects are readonly through the association. # [:validate] # If false, don't validate the associated objects when saving the parent object. true by default. + # [:autosave] + # If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default. + # # Option examples: # has_many :comments, :order => "posted_on" # has_many :comments, :include => :author @@ -865,6 +884,8 @@ module ActiveRecord # If true, the associated object is readonly through the association. # [:validate] # If false, don't validate the associated object when saving the parent object. +false+ by default. + # [:autosave] + # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default. # # Option examples: # has_one :credit_card, :dependent => :destroy # destroys the associated credit card @@ -882,13 +903,10 @@ module ActiveRecord else reflection = create_has_one_reflection(association_id, options) - ivar = "@#{reflection.name}" - method_name = "has_one_after_save_for_#{reflection.name}".to_sym define_method(method_name) do - association = instance_variable_get(ivar) if instance_variable_defined?(ivar) - - if !association.nil? && (new_record? || association.new_record? || association[reflection.primary_key_name] != id) + association = association_instance_get(reflection.name) + if association && (new_record? || association.new_record? || association[reflection.primary_key_name] != id) association[reflection.primary_key_name] = id association.save(true) end @@ -979,6 +997,8 @@ module ActiveRecord # If true, the associated object is readonly through the association. # [:validate] # If false, don't validate the associated objects when saving the parent object. +false+ by default. + # [:autosave] + # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default. # # Option examples: # belongs_to :firm, :foreign_key => "client_of" @@ -991,15 +1011,12 @@ module ActiveRecord def belongs_to(association_id, options = {}) reflection = create_belongs_to_reflection(association_id, options) - ivar = "@#{reflection.name}" - if reflection.options[:polymorphic] association_accessor_methods(reflection, BelongsToPolymorphicAssociation) method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym define_method(method_name) do - association = instance_variable_get(ivar) if instance_variable_defined?(ivar) - + association = association_instance_get(reflection.name) if association && association.target if association.new_record? association.save(true) @@ -1019,9 +1036,7 @@ module ActiveRecord method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym define_method(method_name) do - association = instance_variable_get(ivar) if instance_variable_defined?(ivar) - - if !association.nil? + if association = association_instance_get(reflection.name) if association.new_record? association.save(true) end @@ -1196,6 +1211,8 @@ module ActiveRecord # If true, all the associated objects are readonly through the association. # [:validate] # If false, don't validate the associated objects when saving the parent object. +true+ by default. + # [:autosave] + # If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default. # # Option examples: # has_and_belongs_to_many :projects @@ -1243,33 +1260,30 @@ module ActiveRecord end def association_accessor_methods(reflection, association_proxy_class) - ivar = "@#{reflection.name}" - define_method(reflection.name) do |*params| force_reload = params.first unless params.empty? - - association = instance_variable_get(ivar) if instance_variable_defined?(ivar) + association = association_instance_get(reflection.name) if association.nil? || force_reload association = association_proxy_class.new(self, reflection) retval = association.reload if retval.nil? and association_proxy_class == BelongsToAssociation - instance_variable_set(ivar, nil) + association_instance_set(reflection.name, nil) return nil end - instance_variable_set(ivar, association) + association_instance_set(reflection.name, association) end association.target.nil? ? nil : association end define_method("loaded_#{reflection.name}?") do - association = instance_variable_get(ivar) if instance_variable_defined?(ivar) + association = association_instance_get(reflection.name) association && association.loaded? end define_method("#{reflection.name}=") do |new_value| - association = instance_variable_get(ivar) if instance_variable_defined?(ivar) + association = association_instance_get(reflection.name) if association.nil? || association.target != new_value association = association_proxy_class.new(self, reflection) @@ -1280,7 +1294,7 @@ module ActiveRecord self.send(reflection.name, new_value) else association.replace(new_value) - instance_variable_set(ivar, new_value.nil? ? nil : association) + association_instance_set(reflection.name, new_value.nil? ? nil : association) end end @@ -1288,20 +1302,18 @@ module ActiveRecord return if target.nil? and association_proxy_class == BelongsToAssociation association = association_proxy_class.new(self, reflection) association.target = target - instance_variable_set(ivar, association) + association_instance_set(reflection.name, association) end end def collection_reader_method(reflection, association_proxy_class) define_method(reflection.name) do |*params| - ivar = "@#{reflection.name}" - force_reload = params.first unless params.empty? - association = instance_variable_get(ivar) if instance_variable_defined?(ivar) + association = association_instance_get(reflection.name) - unless association.respond_to?(:loaded?) + unless association association = association_proxy_class.new(self, reflection) - instance_variable_set(ivar, association) + association_instance_set(reflection.name, association) end association.reload if force_reload @@ -1339,8 +1351,7 @@ module ActiveRecord def add_single_associated_validation_callbacks(association_name) method_name = "validate_associated_records_for_#{association_name}".to_sym define_method(method_name) do - association = instance_variable_get("@#{association_name}") - if !association.nil? + if association = association_instance_get(association_name) errors.add association_name unless association.target.nil? || association.valid? end end @@ -1350,12 +1361,10 @@ module ActiveRecord def add_multiple_associated_validation_callbacks(association_name) method_name = "validate_associated_records_for_#{association_name}".to_sym - ivar = "@#{association_name}" - define_method(method_name) do - association = instance_variable_get(ivar) if instance_variable_defined?(ivar) + association = association_instance_get(association_name) - if association.respond_to?(:loaded?) + if association if new_record? association elsif association.loaded? @@ -1372,8 +1381,6 @@ module ActiveRecord end def add_multiple_associated_save_callbacks(association_name) - ivar = "@#{association_name}" - method_name = "before_save_associated_records_for_#{association_name}".to_sym define_method(method_name) do @new_record_before_save = new_record? @@ -1383,13 +1390,13 @@ module ActiveRecord method_name = "after_create_or_update_associated_records_for_#{association_name}".to_sym define_method(method_name) do - association = instance_variable_get(ivar) if instance_variable_defined?(ivar) + association = association_instance_get(association_name) records_to_save = if @new_record_before_save association - elsif association.respond_to?(:loaded?) && association.loaded? + elsif association && association.loaded? association.select { |record| record.new_record? } - elsif association.respond_to?(:loaded?) && !association.loaded? + elsif association && !association.loaded? association.target.select { |record| record.new_record? } else [] @@ -1407,15 +1414,13 @@ module ActiveRecord def association_constructor_method(constructor, reflection, association_proxy_class) define_method("#{constructor}_#{reflection.name}") do |*params| - ivar = "@#{reflection.name}" - attributees = params.first unless params.empty? replace_existing = params[1].nil? ? true : params[1] - association = instance_variable_get(ivar) if instance_variable_defined?(ivar) + association = association_instance_get(reflection.name) - if association.nil? + unless association association = association_proxy_class.new(self, reflection) - instance_variable_set(ivar, association) + association_instance_set(reflection.name, association) end if association_proxy_class == HasOneAssociation -- cgit v1.2.3