From f4f7d1997a8f6f09aaaa7a0c0dacb94dcf34e8cf Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Thu, 27 May 2021 16:55:11 -0700 Subject: [PATCH] Ignore unique constraint violations when adding associated objects in mtm_update These can happen if the user clicks back after an update and then resubmits the form (and certainly other cases). Add a similar test that removing already removed associated objects also does not raise an error. --- CHANGELOG | 2 ++ lib/autoforme/models/sequel.rb | 6 +++- spec/mtm_spec.rb | 64 ++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index e8f4418..259f2f5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ === master +* Ignore unique constraint violations when adding associated objects in mtm_update (jeremyevans) + * Handle search fields that cannot be typecast correctly by returning no results (jeremyevans) === 1.9.1 (2019-07-22) diff --git a/lib/autoforme/models/sequel.rb b/lib/autoforme/models/sequel.rb index 0f01aa8..85b8e75 100644 --- a/lib/autoforme/models/sequel.rb +++ b/lib/autoforme/models/sequel.rb @@ -300,7 +300,11 @@ def mtm_update(request, assoc, obj, add, remove) ids.each do |id| next if id.to_s.empty? ret = assoc_class ? assoc_class.with_pk(:association, request, id) : obj.send(:_apply_association_options, ref, ref.associated_class.dataset.clone).with_pk!(id) - obj.send(meth, ret) + begin + model.db.transaction(:savepoint=>true){obj.send(meth, ret)} + rescue S::UniqueConstraintViolation + # Already added, safe to ignore + end end end end diff --git a/spec/mtm_spec.rb b/spec/mtm_spec.rb index 83a1998..1268edf 100644 --- a/spec/mtm_spec.rb +++ b/spec/mtm_spec.rb @@ -507,3 +507,67 @@ def req.xhr?; action_type == 'mtm_update' end page.all('select')[1].all('option').map{|s| s.text}.must_equal ["Album2", "Album3"] end end + +describe AutoForme do + before(:all) do + db_setup(:artists=>[[:name, :string]], :albums=>[[:name, :string]], :albums_artists=>proc{column :album_id, :integer, :table=>:albums; column :artist_id, :integer, :table=>:artists; primary_key [:album_id, :artist_id]}) + model_setup(:Artist=>[:artists, [[:many_to_many, :albums]]], :Album=>[:albums, [[:many_to_many, :artists]]]) + end + after(:all) do + Object.send(:remove_const, :Album) + Object.send(:remove_const, :Artist) + end + + it "should handle unique constraint violation errors when adding associated objects" do + app_setup do + model Artist do + mtm_associations :albums + end + model Album + end + + artist = Artist.create(:name=>'Artist1') + album = Album.create(:name=>'Album1') + + visit("/Artist/mtm_edit") + page.title.must_equal 'Artist - Many To Many Edit' + select("Artist1") + click_button "Edit" + + find('h2').text.must_equal 'Edit Albums for Artist1' + page.all('select')[0].all('option').map{|s| s.text}.must_equal ["Album1"] + page.all('select')[1].all('option').map{|s| s.text}.must_equal [] + select("Album1", :from=>"Associate With") + artist.add_album(album) + click_button "Update" + page.html.must_include 'Updated albums association for Artist' + Artist.first.albums.map{|x| x.name}.must_equal %w'Album1' + end + + it "should handle unique constraint violation errors when adding associated objects" do + app_setup do + model Artist do + mtm_associations :albums + end + model Album + end + + artist = Artist.create(:name=>'Artist1') + album = Album.create(:name=>'Album1') + artist.add_album(album) + + visit("/Artist/mtm_edit") + page.title.must_equal 'Artist - Many To Many Edit' + select("Artist1") + click_button "Edit" + + find('h2').text.must_equal 'Edit Albums for Artist1' + page.all('select')[0].all('option').map{|s| s.text}.must_equal [] + page.all('select')[1].all('option').map{|s| s.text}.must_equal ["Album1"] + select("Album1", :from=>"Disassociate From") + artist.remove_album(album) + click_button "Update" + page.html.must_include 'Updated albums association for Artist' + Artist.first.albums.map{|x| x.name}.must_equal [] + end +end