Skip to content

Instantly share code, notes, and snippets.

@y-yagi
Forked from eileencodes/gist:5b0a2fe011dcff6203fe
Last active August 29, 2015 14:07
Show Gist options
  • Save y-yagi/2b93454fe96aa195a3b5 to your computer and use it in GitHub Desktop.
Save y-yagi/2b93454fe96aa195a3b5 to your computer and use it in GitHub Desktop.

Testing behavior of a CollectionProxy based on :dependent option, delete method used and assocation type (:has_many vs :has_many :through)

:has_many association

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

:has_many association and destroy_all method

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]]

========================================================================================

Now let's do a :has_many association with delete_all.

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]]

========================================================================================

:has_many :through Association

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

:has_many :through association with destroy

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]]

========================================================================================

:has_many :through association with delete_all

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]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment