Ecto can call Repo.delete/2 internally during association management, for example through cast_assoc/3 or put_assoc/4 when an association uses a delete-triggering on_replace strategy such as :delete or :delete_if_exists.

Lazarus intercepts that flow:

  • If the child schema has a deleted_at field and the parent schema does not opt that association into @hard_delete_on_replace, it is soft-deleted
  • Otherwise, it is hard-deleted

That means delete-triggering on_replace flows stay data-preserving whenever the child schema is soft-delete-aware, but keep the default hard-delete behaviour when they are not.

Forcing hard-delete for specific associations

The @hard_delete_on_replace attribute on the parent schema overrides the soft-delete behavior, forcing hard-deletion even for schemas that have deleted_at.

@hard_delete_on_replace [:ratings]

schema "posts" do
  # Default: soft-deleted (Comment has deleted_at)
  has_many :comments, Comment, on_replace: :delete_if_exists

  # Override: hard-deleted (even though Rating has deleted_at)
  has_many :ratings, Rating, on_replace: :delete_if_exists
end