Testing behavior of a CollectionProxy based on :dependent
option, delete method used and assocation type (:has_many
vs :has_many :through
)
class Category < ActiveRecord::Base
has_many :contacts, through: :categorizations
has_many :categorizations, dependent: DEPENDENT
end
class Contact < ActiveRecord::Base
has_many :categories, through: :categorizations
has_many :categorizations, dependent: DEPENDENT
end
class Categorization < ActiveRecord::Base
belongs_to :category
belongs_to :contact
end
category.categorizations.destroy_all
with a destroy
dependency. For a has_many :through
with a :destroy
dependency and destroy_all
is called on the associated record, records are deleted individually and do fire callbacks.
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 1]]
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 2]]
========================================================================================
Same with
category.categorizations.destroy_all
with a :delete_all
dependency, the dependency is ignored (the SQL does not change, and uses the destroy strategy) and callbacks are fired.
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 1]]
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 2]]
========================================================================================
Same with
category.categorizations.destroy_all
with a :nullify
dependency, the dependency is ignored and callbacks are fired.
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 1]]
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 2]]
========================================================================================
Same with
category.categorizations.destroy_all
with NO dependency, the dependency is ignored and callbacks are fired.
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 1]]
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 2]]
========================================================================================
category.categorizations.delete_all
with a :destroy
dependency appears to ignore the dependency of :destroy
. This is correct, delete_all
was changed to NEVER fire callbacks and to not instantiate objects so it behaves the same as delete_all
with a :delete_all
dependency.
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."category_id" = ? [["category_id", 1]]
========================================================================================
category.categorizations.delete_all
with a :delete_all
dependency does not fire callbacks and does not instantiate objects.
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."category_id" = ? [["category_id", 1]]
========================================================================================
category.categorizations.delete_all
with a :nullify
dependency, honors the dependency by nullifying the records instead of deleting them. Callbacks are not fired and records are not instantiated.
SQL (0.0ms) UPDATE "categorizations" SET "category_id" = NULL WHERE "categorizations"."category_id" = ? [["category_id", 1]]
========================================================================================
category.categorizations.delete_all
with NO dependency, honors the default :nullify
dependency by nullifying the records instead of deleting them. Callbacks are not fired and records are not instantiated.
SQL (0.0ms) UPDATE "categorizations" SET "category_id" = NULL WHERE "categorizations"."category_id" = ? [["category_id", 1]]
========================================================================================
Now looking at has_many :through
associations . Here's where things get tricky. To use the dependency
it needs to be on the through association not the :has_many
association.
Ex:
class Category < ActiveRecord::Base
has_many :contacts, through: :categorizations, dependent: DEPENDENT
has_many :categorizations
end
class Contact < ActiveRecord::Base
has_many :categories, through: :categorizations, dependent: DEPENDENT
has_many :categorizations
end
class Categorization < ActiveRecord::Base
belongs_to :category
belongs_to :contact
end
category.contacts.destroy_all
with a destroy
dependency uses the destroy strategy. Fires callbacks and instantiates records. You'll notice categorizations are removed and not contacts. This is correct. Category does not own contacts and therefore only has the right to delete the records holding contact and category together; the categorizations.
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 1]]
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 2]]
========================================================================================
Same with
category.contacts.destroy_all
with a :delete_all
dependency, the dependency is ignored and callbacks are fired.
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 1]]
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 2]]
========================================================================================
Same with
category.contacts.destroy_all
with a :nullify
dependency, the dependency is ignored and callbacks are fired.
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 1]]
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 2]]
========================================================================================
Same with
category.contacts.destroy_all
with NO dependency, the dependency is ignored and callbacks are fired.
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 1]]
SQL (0.0ms) DELETE FROM "categorizations" WHERE "categorizations"."id" = ? [["id", 2]]
========================================================================================
category.contacts.delete_all
with a :destroy
dependency honors the delete_all
method only because delete_all
should never instantiate records or fire callbacks.
SQL (0.1ms) DELETE FROM "categorizations" WHERE "categorizations"."category_id" = ? AND "categorizations"."contact_id" IN (1, 2) [["category_id", 1]]
========================================================================================
category.contacts.delete_all
with a :delete_all
dependency deletes according to the delete_all strategy.
SQL (0.1ms) DELETE FROM "categorizations" WHERE "categorizations"."category_id" = ? AND "categorizations"."contact_id" IN (1, 2) [["category_id", 1]]
========================================================================================
category.contacts.delete_all
with a :nullify
dependency honors the :nullify
strategy and sets the records to null instead of deleting them.
SQL (0.1ms) UPDATE "categorizations" SET "contact_id" = NULL WHERE "categorizations"."category_id" = ? AND "categorizations"."contact_id" IN (1, 2) [["category_id", 1]]
========================================================================================
category.contacts.delete_all
with NO dependency uses the :delete_all
dependency strategy because that is the default for a :has_many
relationship.
SQL (0.1ms) DELETE FROM "categorizations" WHERE "categorizations"."category_id" = ? AND "categorizations"."contact_id" IN (1, 2) [["category_id", 1]]