1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activerecord/test/models/pirate.rb
Tekin Suleyman 5a3e34ed1d
Ensure Contextual validations fire on associations
If a custom validation context is used, the validations on dependent
association records should fire regardless of whether those record have
`changed_for_autosave?` or not as the use of a custom validation context
increases the chance that validations for the context will be different
to those from when the record was first saved.

6ea80b6 changed the autosave behaviour for single associations such that
validations would only fire on an associated record if the record was
`changed_for_autosave?`. This brought the behaviour inline with that for
collection associations, but introduce a regression in that validations
would no longer fire on dependent associations, even when using a custom
validation context.

This change updates the behaviour for both single and collection
associations.

This commit also updates another related regression test that became a
potential false-positive when 6ea80b6 was merged. The original test was
written to protect against non-custom validations firing on associations
(see #14106). At the time validations on autosaving singular
associations would always fire, even when the association was not dirty,
whereas after 6ea80b6 the validations only fire if the association is
dirty. This update to the test makes the association record dirty to
ensure the validations will fire so that the code responsible for
excluding non-custom contexts is correctly exercised.
2019-10-04 12:17:53 +01:00

116 lines
4.8 KiB
Ruby

# frozen_string_literal: true
class Pirate < ActiveRecord::Base
belongs_to :parrot, validate: true
belongs_to :non_validated_parrot, class_name: "Parrot"
has_and_belongs_to_many :parrots, -> { order("parrots.id ASC") }, validate: true
has_and_belongs_to_many :non_validated_parrots, class_name: "Parrot"
has_and_belongs_to_many :parrots_with_method_callbacks, class_name: "Parrot",
before_add: :log_before_add,
after_add: :log_after_add,
before_remove: :log_before_remove,
after_remove: :log_after_remove
has_and_belongs_to_many :parrots_with_proc_callbacks, class_name: "Parrot",
before_add: proc { |p, pa| p.ship_log << "before_adding_proc_parrot_#{pa.id || '<new>'}" },
after_add: proc { |p, pa| p.ship_log << "after_adding_proc_parrot_#{pa.id || '<new>'}" },
before_remove: proc { |p, pa| p.ship_log << "before_removing_proc_parrot_#{pa.id}" },
after_remove: proc { |p, pa| p.ship_log << "after_removing_proc_parrot_#{pa.id}" }
has_and_belongs_to_many :autosaved_parrots, class_name: "Parrot", autosave: true
module PostTreasuresExtension
def build(attributes = {})
super({ name: "from extension" }.merge(attributes))
end
end
has_many :treasures, as: :looter, extend: PostTreasuresExtension
has_many :treasure_estimates, through: :treasures, source: :price_estimates
has_one :ship
has_one :update_only_ship, class_name: "Ship"
has_one :non_validated_ship, class_name: "Ship"
has_many :birds, -> { order("birds.id ASC") }
has_many :birds_with_method_callbacks, class_name: "Bird",
before_add: :log_before_add,
after_add: :log_after_add,
before_remove: :log_before_remove,
after_remove: :log_after_remove
has_many :birds_with_proc_callbacks, class_name: "Bird",
before_add: proc { |p, b| p.ship_log << "before_adding_proc_bird_#{b.id || '<new>'}" },
after_add: proc { |p, b| p.ship_log << "after_adding_proc_bird_#{b.id || '<new>'}" },
before_remove: proc { |p, b| p.ship_log << "before_removing_proc_bird_#{b.id}" },
after_remove: proc { |p, b| p.ship_log << "after_removing_proc_bird_#{b.id}" }
has_many :birds_with_reject_all_blank, class_name: "Bird"
has_one :foo_bulb, -> { where name: "foo" }, foreign_key: :car_id, class_name: "Bulb"
accepts_nested_attributes_for :parrots, :birds, allow_destroy: true, reject_if: proc(&:empty?)
accepts_nested_attributes_for :ship, allow_destroy: true, reject_if: proc(&:empty?)
accepts_nested_attributes_for :update_only_ship, update_only: true
accepts_nested_attributes_for :parrots_with_method_callbacks, :parrots_with_proc_callbacks,
:birds_with_method_callbacks, :birds_with_proc_callbacks, allow_destroy: true
accepts_nested_attributes_for :birds_with_reject_all_blank, reject_if: :all_blank
validates_presence_of :catchphrase
def ship_log
@ship_log ||= []
end
def reject_empty_ships_on_create(attributes)
attributes.delete("_reject_me_if_new").present? && !persisted?
end
attr_accessor :cancel_save_from_callback, :parrots_limit
before_save :cancel_save_callback_method, if: :cancel_save_from_callback
def cancel_save_callback_method
throw(:abort)
end
private
def log_before_add(record)
log(record, "before_adding_method")
end
def log_after_add(record)
log(record, "after_adding_method")
end
def log_before_remove(record)
log(record, "before_removing_method")
end
def log_after_remove(record)
log(record, "after_removing_method")
end
def log(record, callback)
ship_log << "#{callback}_#{record.class.name.downcase}_#{record.id || '<new>'}"
end
end
class DestructivePirate < Pirate
has_one :dependent_ship, class_name: "Ship", foreign_key: :pirate_id, dependent: :destroy
end
class FamousPirate < ActiveRecord::Base
self.table_name = "pirates"
has_many :famous_ships, inverse_of: :famous_pirate, foreign_key: :pirate_id
validates_presence_of :catchphrase, on: :conference
end
class SpacePirate < ActiveRecord::Base
self.table_name = "pirates"
belongs_to :parrot
belongs_to :parrot_with_annotation, -> { annotate("that tells jokes") }, class_name: :Parrot, foreign_key: :parrot_id
has_and_belongs_to_many :parrots, foreign_key: :pirate_id
has_and_belongs_to_many :parrots_with_annotation, -> { annotate("that are very colorful") }, class_name: :Parrot, foreign_key: :pirate_id
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_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
has_many :treasure_estimates_with_annotation, -> { annotate("yarrr") }, through: :treasures, source: :price_estimates
end