Skip to main content

guides/assoc-replace.md

# Assoc Replace

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`.

```elixir
@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
```