diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index edc48bd2aaad7..d25a197562091 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,12 @@ +* Propagate `save(validate:)` option for `:has_many` associations on autosave. + + `save(validate:)` option is propagated to associated records during `autosave` when `autosave` option is + `nil`. Affects only `:has_many` associations. + + Fixes #43400 + + *Jacopo Beschi* + * Add support for setting the filename of the schema or structure dump in the database config. Applications may now set their the filename or path of the schema / structure dump file in their database configuration. diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 9e9cc4c79d232..88bb3fae42ef8 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -409,7 +409,11 @@ def save_collection_association(reflection) if autosave saved = association.insert_record(record, false) elsif !reflection.nested? - association_saved = association.insert_record(record) + association_saved = if @_save_options.key?(:validate) + association.insert_record(record, @_save_options[:validate]) + else + association.insert_record(record) + end if reflection.validate? errors.add(reflection.name) unless association_saved diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 818515b4e02ee..cf5d80a1642fb 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -44,13 +44,17 @@ module Validations # The regular {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] method is replaced # with this when the validations module is mixed in, which it is by default. def save(**options) - perform_validations(options) ? super : false + with_save_options(options) do + perform_validations ? super : false + end end # Attempts to save the record just like {ActiveRecord::Base#save}[rdoc-ref:Base#save] but # will raise an ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid. def save!(**options) - perform_validations(options) ? super : raise_validation_error + with_save_options(options) do + perform_validations ? super : raise_validation_error + end end # Runs all the validations within the specified context. Returns +true+ if @@ -72,6 +76,13 @@ def valid?(context = nil) alias_method :validate, :valid? private + def with_save_options(options) + @_save_options, previous_save_options = options, @_save_options + yield + ensure + @_save_options = previous_save_options + end + def default_validation_context new_record? ? :create : :update end @@ -80,8 +91,8 @@ def raise_validation_error raise(RecordInvalid.new(self)) end - def perform_validations(options = {}) - options[:validate] == false || valid?(options[:context]) + def perform_validations + @_save_options[:validate] == false || valid?(@_save_options[:context]) end end end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index ef5c71dd46b2f..982f7e4db51bb 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -1847,6 +1847,30 @@ def test_validations_still_fire_on_unchanged_association_with_custom_validation_ assert pirate.valid? assert_not pirate.valid?(:conference) end + + test "propagates (validate: false) option to associations when autosave is nil" do + pirate = SpacePirate.create! + pirate.birds.build + + assert_equal false, pirate.save + assert_equal true, pirate.save(validate: false) + end + + test "propagates (validate: false) option to associations when autosave is true" do + pirate = SpacePirate.create! + pirate.birds_with_autosave.build + + assert_equal false, pirate.save + assert_equal true, pirate.save(validate: false) + end + + test "propagates (validate: false) option to associations when autosave is false" do + pirate = SpacePirate.create! + pirate.birds_without_autosave.build + + assert_equal false, pirate.save + assert_equal true, pirate.save(validate: false) + end end class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::TestCase diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 6b903bc19f633..56c86d4165b67 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -109,6 +109,8 @@ class SpacePirate < ActiveRecord::Base has_one :ship, foreign_key: :pirate_id has_one :ship_with_annotation, -> { annotate("that is a rocket") }, class_name: :Ship, foreign_key: :pirate_id has_many :birds, foreign_key: :pirate_id + has_many :birds_with_autosave, foreign_key: :pirate_id, autosave: true, class_name: :Bird + has_many :birds_without_autosave, foreign_key: :pirate_id, autosave: false, class_name: :Bird has_many :birds_with_annotation, -> { annotate("that are also parrots") }, class_name: :Bird, foreign_key: :pirate_id has_many :treasures, as: :looter has_many :treasure_estimates, through: :treasures, source: :price_estimates