Skip to content

Commit f9133f7

Browse files
committed
Revert "Merge pull request #49056 from joshuay03/raise-on-duplicate-accepts-nested-attributes-for"
This reverts commit b6166e8, reversing changes made to 5415d3a. Reason: The reverted behavior broke the case where the `#accepts_nested_attributes_for` was defined in a concern and where overridden in the class that included the concern.
1 parent aa093f7 commit f9133f7

File tree

3 files changed

+9
-53
lines changed

3 files changed

+9
-53
lines changed

activerecord/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
* Revert the change to raise an `ArgumentError` when `#accepts_nested_attributes_for` is declared more than once for
2+
an association in the same class.
3+
4+
The reverted behavior broke the case where the `#accepts_nested_attributes_for` was defined in a concern and
5+
where overridden in the class that included the concern.
6+
7+
*Rafael Mendonça França*
8+
9+
110
## Rails 7.1.0.rc1 (September 27, 2023) ##
211

312
* Better naming for unique constraints support.

activerecord/lib/active_record/nested_attributes.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -355,17 +355,12 @@ def accepts_nested_attributes_for(*attr_names)
355355
options.update(attr_names.extract_options!)
356356
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
357357
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
358-
options[:class] = self
359358

360359
attr_names.each do |association_name|
361360
if reflection = _reflect_on_association(association_name)
362361
reflection.autosave = true
363362
define_autosave_validation_callbacks(reflection)
364363

365-
if nested_attributes_options.dig(association_name.to_sym, :class) == self
366-
raise ArgumentError, "Already declared #{association_name} as an accepts_nested_attributes association for this class."
367-
end
368-
369364
nested_attributes_options = self.nested_attributes_options.dup
370365
nested_attributes_options[association_name.to_sym] = options
371366
self.nested_attributes_options = nested_attributes_options

activerecord/test/cases/nested_attributes_test.rb

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
class TestNestedAttributesInGeneral < ActiveRecord::TestCase
2020
teardown do
21-
Pirate.nested_attributes_options.delete :ship
2221
Pirate.accepts_nested_attributes_for :ship, allow_destroy: true, reject_if: proc(&:empty?)
2322
end
2423

@@ -75,7 +74,6 @@ def test_should_raise_an_UnknownAttributeError_for_non_existing_nested_attribute
7574
end
7675

7776
def test_should_disable_allow_destroy_by_default
78-
Pirate.nested_attributes_options.delete :ship
7977
Pirate.accepts_nested_attributes_for :ship
8078

8179
pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
@@ -94,7 +92,6 @@ def test_a_model_should_respond_to_underscore_destroy_and_return_if_it_is_marked
9492
end
9593

9694
def test_reject_if_method_without_arguments
97-
Pirate.nested_attributes_options.delete :ship
9895
Pirate.accepts_nested_attributes_for :ship, reject_if: :new_record?
9996

10097
pirate = Pirate.new(catchphrase: "Stop wastin' me time")
@@ -103,7 +100,6 @@ def test_reject_if_method_without_arguments
103100
end
104101

105102
def test_reject_if_method_with_arguments
106-
Pirate.nested_attributes_options.delete :ship
107103
Pirate.accepts_nested_attributes_for :ship, reject_if: :reject_empty_ships_on_create
108104

109105
pirate = Pirate.new(catchphrase: "Stop wastin' me time")
@@ -117,7 +113,6 @@ def test_reject_if_method_with_arguments
117113
end
118114

119115
def test_reject_if_with_indifferent_keys
120-
Pirate.nested_attributes_options.delete :ship
121116
Pirate.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| attributes[:name].blank? }
122117

123118
pirate = Pirate.new(catchphrase: "Stop wastin' me time")
@@ -126,7 +121,6 @@ def test_reject_if_with_indifferent_keys
126121
end
127122

128123
def test_reject_if_with_a_proc_which_returns_true_always_for_has_one
129-
Pirate.nested_attributes_options.delete :ship
130124
Pirate.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| true }
131125
pirate = Pirate.create(catchphrase: "Stop wastin' me time")
132126
ship = pirate.create_ship(name: "s1")
@@ -149,7 +143,6 @@ def test_do_not_allow_assigning_foreign_key_when_reusing_existing_new_record
149143
end
150144

151145
def test_reject_if_with_a_proc_which_returns_true_always_for_has_many
152-
Human.nested_attributes_options.delete :interests
153146
Human.accepts_nested_attributes_for :interests, reject_if: proc { |attributes| true }
154147
human = Human.create(name: "John")
155148
interest = human.interests.create(topic: "photography")
@@ -158,7 +151,6 @@ def test_reject_if_with_a_proc_which_returns_true_always_for_has_many
158151
end
159152

160153
def test_destroy_works_independent_of_reject_if
161-
Human.nested_attributes_options.delete :interests
162154
Human.accepts_nested_attributes_for :interests, reject_if: proc { |attributes| true }, allow_destroy: true
163155
human = Human.create(name: "Jon")
164156
interest = human.interests.create(topic: "the ladies")
@@ -167,7 +159,6 @@ def test_destroy_works_independent_of_reject_if
167159
end
168160

169161
def test_reject_if_is_not_short_circuited_if_allow_destroy_is_false
170-
Pirate.nested_attributes_options.delete :ship
171162
Pirate.accepts_nested_attributes_for :ship, reject_if: ->(a) { a[:name] == "The Golden Hind" }, allow_destroy: false
172163

173164
pirate = Pirate.create!(catchphrase: "Stop wastin' me time", ship_attributes: { name: "White Pearl", _destroy: "1" })
@@ -181,7 +172,6 @@ def test_reject_if_is_not_short_circuited_if_allow_destroy_is_false
181172
end
182173

183174
def test_has_many_association_updating_a_single_record
184-
Human.nested_attributes_options.delete(:interests)
185175
Human.accepts_nested_attributes_for(:interests)
186176
human = Human.create(name: "John")
187177
interest = human.interests.create(topic: "photography")
@@ -190,7 +180,6 @@ def test_has_many_association_updating_a_single_record
190180
end
191181

192182
def test_reject_if_with_blank_nested_attributes_id
193-
Pirate.nested_attributes_options.delete :ship
194183
# When using a select list to choose an existing 'ship' id, with include_blank: true
195184
Pirate.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| attributes[:id].blank? }
196185

@@ -200,7 +189,6 @@ def test_reject_if_with_blank_nested_attributes_id
200189
end
201190

202191
def test_first_and_array_index_zero_methods_return_the_same_value_when_nested_attributes_are_set_to_update_existing_record
203-
Human.nested_attributes_options.delete(:interests)
204192
Human.accepts_nested_attributes_for(:interests)
205193
human = Human.create(name: "John")
206194
interest = human.interests.create topic: "gardening"
@@ -210,7 +198,6 @@ def test_first_and_array_index_zero_methods_return_the_same_value_when_nested_at
210198
end
211199

212200
def test_allows_class_to_override_setter_and_call_super
213-
Pirate.nested_attributes_options.delete :parrot
214201
mean_pirate_class = Class.new(Pirate) do
215202
accepts_nested_attributes_for :parrot
216203
def parrot_attributes=(attrs)
@@ -235,7 +222,6 @@ def test_accepts_nested_attributes_for_can_be_overridden_in_subclasses
235222
end
236223

237224
def test_should_not_create_duplicates_with_create_with
238-
Human.nested_attributes_options.delete(:interests)
239225
Human.accepts_nested_attributes_for(:interests)
240226

241227
assert_difference("Interest.count", 1) do
@@ -352,14 +338,12 @@ def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
352338
end
353339

354340
def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
355-
Pirate.nested_attributes_options.delete :ship
356341
Pirate.accepts_nested_attributes_for :ship, allow_destroy: false, reject_if: proc(&:empty?)
357342

358343
@pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: "1" })
359344

360345
assert_equal @ship, @pirate.reload.ship
361346

362-
Pirate.nested_attributes_options.delete :ship
363347
Pirate.accepts_nested_attributes_for :ship, allow_destroy: true, reject_if: proc(&:empty?)
364348
end
365349

@@ -427,7 +411,6 @@ def test_should_update_existing_when_update_only_is_true_and_id_is_given
427411
end
428412

429413
def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction
430-
Pirate.nested_attributes_options.delete :update_only_ship
431414
Pirate.accepts_nested_attributes_for :update_only_ship, update_only: true, allow_destroy: true
432415
@ship.delete
433416
@ship = @pirate.create_update_only_ship(name: "Nights Dirty Lightning")
@@ -437,7 +420,6 @@ def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is
437420
assert_nil @pirate.reload.ship
438421
assert_raise(ActiveRecord::RecordNotFound) { Ship.find(@ship.id) }
439422

440-
Pirate.nested_attributes_options.delete :update_only_ship
441423
Pirate.accepts_nested_attributes_for :update_only_ship, update_only: true, allow_destroy: false
442424
end
443425
end
@@ -550,13 +532,11 @@ def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
550532
end
551533

552534
def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
553-
Ship.nested_attributes_options.delete :pirate
554535
Ship.accepts_nested_attributes_for :pirate, allow_destroy: false, reject_if: proc(&:empty?)
555536

556537
@ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: "1" })
557538
assert_nothing_raised { @ship.pirate.reload }
558539
ensure
559-
Ship.nested_attributes_options.delete :pirate
560540
Ship.accepts_nested_attributes_for :pirate, allow_destroy: true, reject_if: proc(&:empty?)
561541
end
562542

@@ -608,7 +588,6 @@ def test_should_update_existing_when_update_only_is_true_and_id_is_given
608588
end
609589

610590
def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction
611-
Ship.nested_attributes_options.delete :update_only_pirate
612591
Ship.accepts_nested_attributes_for :update_only_pirate, update_only: true, allow_destroy: true
613592
@pirate.delete
614593
@pirate = @ship.create_update_only_pirate(catchphrase: "Aye")
@@ -617,7 +596,6 @@ def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is
617596

618597
assert_raise(ActiveRecord::RecordNotFound) { @pirate.reload }
619598

620-
Ship.nested_attributes_options.delete :update_only_pirate
621599
Ship.accepts_nested_attributes_for :update_only_pirate, update_only: true, allow_destroy: false
622600
end
623601
end
@@ -842,7 +820,6 @@ def test_should_automatically_enable_autosave_on_the_association
842820
end
843821

844822
def test_validate_presence_of_parent_works_with_inverse_of
845-
Human.nested_attributes_options.delete(:interests)
846823
Human.accepts_nested_attributes_for(:interests)
847824
assert_equal :human, Human.reflect_on_association(:interests).options[:inverse_of]
848825
assert_equal :interests, Interest.reflect_on_association(:human).options[:inverse_of]
@@ -865,7 +842,6 @@ def test_can_use_symbols_as_object_identifier
865842
end
866843

867844
def test_numeric_column_changes_from_zero_to_no_empty_string
868-
Human.nested_attributes_options.delete(:interests)
869845
Human.accepts_nested_attributes_for(:interests)
870846

871847
repair_validations(Interest) do
@@ -933,7 +909,6 @@ def setup
933909

934910
module NestedAttributesLimitTests
935911
def teardown
936-
Pirate.nested_attributes_options.delete :parrots
937912
Pirate.accepts_nested_attributes_for :parrots, allow_destroy: true, reject_if: proc(&:empty?)
938913
end
939914

@@ -958,7 +933,6 @@ def test_limit_with_exceeding_records
958933

959934
class TestNestedAttributesLimitNumeric < ActiveRecord::TestCase
960935
def setup
961-
Pirate.nested_attributes_options.delete :parrots
962936
Pirate.accepts_nested_attributes_for :parrots, limit: 2
963937

964938
@pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
@@ -969,7 +943,6 @@ def setup
969943

970944
class TestNestedAttributesLimitSymbol < ActiveRecord::TestCase
971945
def setup
972-
Pirate.nested_attributes_options.delete :parrots
973946
Pirate.accepts_nested_attributes_for :parrots, limit: :parrots_limit
974947

975948
@pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?", parrots_limit: 2)
@@ -980,7 +953,6 @@ def setup
980953

981954
class TestNestedAttributesLimitProc < ActiveRecord::TestCase
982955
def setup
983-
Pirate.nested_attributes_options.delete :parrots
984956
Pirate.accepts_nested_attributes_for :parrots, limit: proc { 2 }
985957

986958
@pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
@@ -993,7 +965,6 @@ class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase
993965
fixtures :owners, :pets
994966

995967
def setup
996-
Owner.nested_attributes_options.delete :pets
997968
Owner.accepts_nested_attributes_for :pets, allow_destroy: true
998969

999970
@owner = owners(:ashley)
@@ -1161,22 +1132,3 @@ def test_should_build_a_new_record_based_on_the_delegated_type
11611132
assert_equal "Hello world!", @entry.entryable.subject
11621133
end
11631134
end
1164-
1165-
class TestPreDeclaredNestedAttributesAssociation < ActiveRecord::TestCase
1166-
setup do
1167-
assert @current_options = Developer.nested_attributes_options[:projects]
1168-
end
1169-
1170-
def test_should_raise_an_argument_error_with_similar_options
1171-
assert_raises ArgumentError do
1172-
Developer.accepts_nested_attributes_for :projects, **@current_options
1173-
end
1174-
end
1175-
1176-
def test_should_raise_an_argument_error_with_varying_options
1177-
assert_equal false, @current_options[:update_only]
1178-
assert_raises ArgumentError do
1179-
Developer.accepts_nested_attributes_for :projects, **@current_options.merge(update_only: true)
1180-
end
1181-
end
1182-
end

0 commit comments

Comments
 (0)