From 07b615fb897017d7acfaafa88606bc88be30f6e4 Mon Sep 17 00:00:00 2001 From: Michael Siebert Date: Mon, 28 Dec 2009 19:02:01 +0100 Subject: Add an :update_only option to accepts_nested_attributes_for for to-one associations. [#2563 state:resolved] Signed-off-by: Eloy Duran --- .../lib/active_record/nested_attributes.rb | 26 +++++++++++++++--- activerecord/test/cases/nested_attributes_test.rb | 31 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 386f0e7ca7..5143e804d1 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -212,6 +212,11 @@ module ActiveRecord # nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords # exception is raised. If omitted, any number associations can be processed. # Note that the :limit option is only applicable to one-to-many associations. + # [:update_only] + # Allows to specify that the an existing record can only be updated. + # A new record in only created when there is no existing record. This + # option only works for on-to-one associations and is ignored for + # collection associations. This option is off by default. # # Examples: # # creates avatar_attributes= @@ -221,9 +226,9 @@ module ActiveRecord # # creates avatar_attributes= and posts_attributes= # accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true def accepts_nested_attributes_for(*attr_names) - options = { :allow_destroy => false } + options = { :allow_destroy => false, :update_only => false } options.update(attr_names.extract_options!) - options.assert_valid_keys(:allow_destroy, :reject_if, :limit) + options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only) attr_names.each do |association_name| if reflection = reflect_on_association(association_name) @@ -288,6 +293,13 @@ module ActiveRecord # record’s id, then the existing record will be modified. Otherwise a new # record will be built. # + # If update_only is true, a new record is only created when no object exists, + # otherwise it will be updated + # + # If update_only is false and the given attributes include an :id + # that matches the existing record’s id, then the existing record will be + # modified. Otherwise a new record will be built. + # # If the given attributes include a matching :id attribute _and_ a # :_destroy key set to a truthy value, then the existing record # will be marked for destruction. @@ -295,7 +307,15 @@ module ActiveRecord options = self.nested_attributes_options[association_name] attributes = attributes.with_indifferent_access - if attributes['id'].blank? + if options[:update_only] + if existing_record = send(association_name) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) + else + unless reject_new_record?(association_name, attributes) + send("build_#{association_name}", attributes.except(*UNASSIGNABLE_KEYS)) + end + end + elsif attributes['id'].blank? unless reject_new_record?(association_name, attributes) method = "build_#{association_name}" if respond_to?(method) diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 5367907fb7..5e4fc2b8d9 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -245,6 +245,37 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_automatically_enable_autosave_on_the_association assert Pirate.reflect_on_association(:ship).options[:autosave] end + + def test_should_accept_update_only_option + Pirate.accepts_nested_attributes_for :ship, :update_only => true + @pirate.update_attribute(:ship_attributes, { :id => @pirate.ship.id, :name => 'Mayflower' }) + + Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } + end + + def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true + Pirate.accepts_nested_attributes_for :ship, :update_only => true + @ship.delete + + assert_difference('Ship.count', 1) do + @pirate.reload.update_attribute(:ship_attributes, { :name => 'Mayflower' }) + end + + Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } + end + + + def test_should_update_existing_when_update_only_is_true_and_no_id_is_given + Pirate.accepts_nested_attributes_for :ship, :update_only => true + + assert_no_difference('Ship.count') do + @pirate.reload.update_attributes(:ship_attributes => { :name => 'Mayflower' }) + end + + assert_equal 'Mayflower', @ship.reload.name + + Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } + end end class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase -- cgit v1.2.3