We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Keep related writes together with `prepare_changes/2`
almirsarajcic
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.
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:
# 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.
copied to clipboard