# Keep related writes together with `prepare_changes/2`

If a database write belongs to a changeset operation, `prepare_changes/2` can run it in the same transaction. A common example is updating a counter when a record is inserted.

```elixir
defmodule MyApp.Blog.Comment do
  use Ecto.Schema

  import Ecto.Changeset
  import Ecto.Query

  alias MyApp.Blog.Post

  schema "comments" do
    field :body, :string
    belongs_to :post, Post

    timestamps()
  end

  @spec changeset(t(), map()) :: Ecto.Changeset.t()
  def changeset(comment, attrs) do
    comment
    |> cast(attrs, [:body, :post_id])
    |> validate_required([:body, :post_id])
    |> prepare_changes(fn changeset ->
      post_id = get_field(changeset, :post_id)
      query = from p in Post, where: p.id == ^post_id
      changeset.repo.update_all(query, inc: [comments_count: 1])

      changeset
    end)
  end
end
```

Ecto runs the callback after validation and only for a valid changeset. Because the callback and the insert share a transaction, a constraint error on the insert also rolls back the counter update.

The callback receives the repo through `changeset.repo`, so the schema does not need to reference `MyApp.Repo` directly. It must return a changeset and may make further changes to it before returning.

Without the callback, the two writes happen separately:

```elixir
# These writes do not succeed or fail as a unit.
{:ok, comment} = Repo.insert(changeset)
Repo.update_all(from(p in Post, where: p.id == ^comment.post_id), inc: [comments_count: 1])

# The prepare callback makes Repo.insert/1 the only call needed here.
Repo.insert(changeset)
```

Use `Ecto.Multi` when the caller coordinates a larger workflow. `prepare_changes/2` fits an operation that should accompany this changeset wherever it is used, whether that is from a context function, a seed script, or an import.

The callback runs for changesets passed to `Repo.insert/2`, `Repo.update/2`, and `Repo.delete/2`. It does not run for `Repo.insert_all/3`, which bypasses changesets.

[Ecto.Changeset.prepare_changes/2 docs](https://hexdocs.pm/ecto/Ecto.Changeset.html#prepare_changes/2)


---

Created by: almirsarajcic
Date: August 19, 2026
URL: https://elixirdrops.net/d/bu8mvaGn
