fix: use Repo.transaction instead of non-existent Repo.transact
This commit is contained in:
+16
-16
@@ -17,18 +17,18 @@ def reset(account, params) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Execute:
|
# Execute:
|
||||||
case Repo.transact(PasswordManager.reset(account, params)) do
|
case Repo.transaction(PasswordManager.reset(account, params)) do
|
||||||
{:ok, %{account: account, log: log}} -> # success
|
{:ok, %{account: account, log: log}} -> # success
|
||||||
{:error, :account, changeset, _} -> # account step failed
|
{:error, :account, changeset, _} -> # account step failed
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
**Why:** Each operation is named. On success, `Repo.transact` returns `{:ok, results_map}` where each key is the name given to that operation. On failure, it returns `{:error, failed_name, failed_value, changes_so_far}`, making it immediately clear which step aborted the transaction and why. This is more precise than a bare transaction function where you'd have to inspect the return value to guess which step failed.
|
**Why:** Each operation is named. On success, `Repo.transaction` returns `{:ok, results_map}` where each key is the name given to that operation. On failure, it returns `{:error, failed_name, failed_value, changes_so_far}`, making it immediately clear which step aborted the transaction and why. This is more precise than a bare transaction function where you'd have to inspect the return value to guess which step failed.
|
||||||
|
|
||||||
**Anti-pattern:** Using an anonymous function with bare `case` statements inside a transaction, where failure attribution is implicit:
|
**Anti-pattern:** Using an anonymous function with bare `case` statements inside a transaction, where failure attribution is implicit:
|
||||||
```elixir
|
```elixir
|
||||||
# BAD — no way to know which operation failed from the return value alone
|
# BAD — no way to know which operation failed from the return value alone
|
||||||
Repo.transact(fn ->
|
Repo.transaction(fn ->
|
||||||
case Repo.update(Account.password_reset_changeset(account, params)) do
|
case Repo.update(Account.password_reset_changeset(account, params)) do
|
||||||
{:ok, account} ->
|
{:ok, account} ->
|
||||||
case Repo.insert(Log.password_reset_changeset(account, params)) do
|
case Repo.insert(Log.password_reset_changeset(account, params)) do
|
||||||
@@ -50,7 +50,7 @@ end)
|
|||||||
**Example — before:**
|
**Example — before:**
|
||||||
```elixir
|
```elixir
|
||||||
def create_user_with_profile(params) do
|
def create_user_with_profile(params) do
|
||||||
Repo.transact(fn ->
|
Repo.transaction(fn ->
|
||||||
case Repo.insert(User.changeset(params)) do
|
case Repo.insert(User.changeset(params)) do
|
||||||
{:ok, user} ->
|
{:ok, user} ->
|
||||||
case Repo.insert(Profile.changeset(user, params)) do
|
case Repo.insert(Profile.changeset(user, params)) do
|
||||||
@@ -71,7 +71,7 @@ def create_user_with_profile(params) do
|
|||||||
|> Multi.insert(:profile, fn %{user: user} ->
|
|> Multi.insert(:profile, fn %{user: user} ->
|
||||||
Profile.changeset(user, params)
|
Profile.changeset(user, params)
|
||||||
end)
|
end)
|
||||||
|> Repo.transact()
|
|> Repo.transaction()
|
||||||
end
|
end
|
||||||
|
|
||||||
# Caller:
|
# Caller:
|
||||||
@@ -86,7 +86,7 @@ end
|
|||||||
|
|
||||||
**Don't use this when:**
|
**Don't use this when:**
|
||||||
- You have a single database operation (just call `Repo.insert/update/delete` directly)
|
- You have a single database operation (just call `Repo.insert/update/delete` directly)
|
||||||
- Operations are simple and sequential with no branching (a plain `Repo.transact(fn -> ... end)` is more readable)
|
- Operations are simple and sequential with no branching (a plain `Repo.transaction(fn -> ... end)` is more readable)
|
||||||
- The overhead of building a Multi struct is not justified by the number of operations
|
- The overhead of building a Multi struct is not justified by the number of operations
|
||||||
|
|
||||||
**Over-application example:**
|
**Over-application example:**
|
||||||
@@ -94,7 +94,7 @@ end
|
|||||||
# Overkill for a single operation
|
# Overkill for a single operation
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> Multi.insert(:user, User.changeset(params))
|
|> Multi.insert(:user, User.changeset(params))
|
||||||
|> Repo.transact()
|
|> Repo.transaction()
|
||||||
```
|
```
|
||||||
|
|
||||||
**Better alternative:**
|
**Better alternative:**
|
||||||
@@ -102,7 +102,7 @@ Multi.new()
|
|||||||
Repo.insert(User.changeset(params))
|
Repo.insert(User.changeset(params))
|
||||||
```
|
```
|
||||||
|
|
||||||
**Why:** `Ecto.Multi` introduces indirection. For simple cases, calling Repo functions directly or using `Repo.transact(fn -> ... end)` is clearer. The named-pipeline form pays off when 3+ operations are involved and failure attribution matters.
|
**Why:** `Ecto.Multi` introduces indirection. For simple cases, calling Repo functions directly or using `Repo.transaction(fn -> ... end)` is clearer. The named-pipeline form pays off when 3+ operations are involved and failure attribution matters.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ end)
|
|||||||
**Example — before:**
|
**Example — before:**
|
||||||
```elixir
|
```elixir
|
||||||
def register_user(params) do
|
def register_user(params) do
|
||||||
Repo.transact(fn ->
|
Repo.transaction(fn ->
|
||||||
{:ok, user} = Repo.insert(User.changeset(params))
|
{:ok, user} = Repo.insert(User.changeset(params))
|
||||||
# This runs outside Multi — if it fails, user was already inserted
|
# This runs outside Multi — if it fails, user was already inserted
|
||||||
case ExternalService.provision(user.id) do
|
case ExternalService.provision(user.id) do
|
||||||
@@ -163,7 +163,7 @@ def register_user(params) do
|
|||||||
|> Multi.run(:provision, fn _repo, %{user: user} ->
|
|> Multi.run(:provision, fn _repo, %{user: user} ->
|
||||||
ExternalService.provision(user.id)
|
ExternalService.provision(user.id)
|
||||||
end)
|
end)
|
||||||
|> Repo.transact()
|
|> Repo.transaction()
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -496,7 +496,7 @@ Enum.reduce(accounts, Multi.new(), fn account, multi ->
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
# Error pattern-matching:
|
# Error pattern-matching:
|
||||||
case Repo.transact(multi) do
|
case Repo.transaction(multi) do
|
||||||
{:ok, results} -> Map.keys(results) # [{:account, 1}, {:account, 2}, ...]
|
{:ok, results} -> Map.keys(results) # [{:account, 1}, {:account, 2}, ...]
|
||||||
{:error, {:account, id}, changeset, _} -> "account #{id} failed"
|
{:error, {:account, id}, changeset, _} -> "account #{id} failed"
|
||||||
end
|
end
|
||||||
@@ -547,7 +547,7 @@ def reset_passwords(accounts, params) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Caller knows exactly which account failed:
|
# Caller knows exactly which account failed:
|
||||||
case Repo.transact(reset_passwords(accounts, params)) do
|
case Repo.transaction(reset_passwords(accounts, params)) do
|
||||||
{:ok, _} -> :ok
|
{:ok, _} -> :ok
|
||||||
{:error, {:account, id}, changeset, _} ->
|
{:error, {:account, id}, changeset, _} ->
|
||||||
Logger.error("Failed to reset account #{id}: #{inspect(changeset.errors)}")
|
Logger.error("Failed to reset account #{id}: #{inspect(changeset.errors)}")
|
||||||
@@ -609,7 +609,7 @@ test "password reset changeset is valid" do
|
|||||||
account = %Account{password: "letmein"}
|
account = %Account{password: "letmein"}
|
||||||
{:ok, %{account: updated}} =
|
{:ok, %{account: updated}} =
|
||||||
PasswordManager.reset(account, valid_params)
|
PasswordManager.reset(account, valid_params)
|
||||||
|> Repo.transact()
|
|> Repo.transaction()
|
||||||
|
|
||||||
# The changeset validity was the whole point, not the DB state
|
# The changeset validity was the whole point, not the DB state
|
||||||
assert updated.password != account.password
|
assert updated.password != account.password
|
||||||
@@ -630,7 +630,7 @@ end
|
|||||||
test "creates user with profile" do
|
test "creates user with profile" do
|
||||||
{:ok, %{user: user, profile: profile}} =
|
{:ok, %{user: user, profile: profile}} =
|
||||||
UserRegistration.multi(valid_params())
|
UserRegistration.multi(valid_params())
|
||||||
|> Repo.transact()
|
|> Repo.transaction()
|
||||||
|
|
||||||
assert user.email == "test@example.com"
|
assert user.email == "test@example.com"
|
||||||
assert profile.user_id == user.id
|
assert profile.user_id == user.id
|
||||||
@@ -685,7 +685,7 @@ test "comment changeset is valid given a post" do
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
**Why:** `Multi.to_list/1` reflects the state of the pipeline at build time. Deferred values (function variants, `Multi.run` callbacks, `Multi.merge` functions) are not evaluated until `Repo.transact` runs them. Test those deferred pieces independently rather than trying to inspect them through `to_list`.
|
**Why:** `Multi.to_list/1` reflects the state of the pipeline at build time. Deferred values (function variants, `Multi.run` callbacks, `Multi.merge` functions) are not evaluated until `Repo.transaction` runs them. Test those deferred pieces independently rather than trying to inspect them through `to_list`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -697,6 +697,6 @@ end
|
|||||||
- If you have reusable Multi fragments to combine → `Multi.append/2` or `Multi.prepend/2`
|
- If you have reusable Multi fragments to combine → `Multi.append/2` or `Multi.prepend/2`
|
||||||
- If you're updating a dynamic collection → tuple keys `{:operation, id}`
|
- If you're updating a dynamic collection → tuple keys `{:operation, id}`
|
||||||
- If you want to validate changesets without hitting the DB → `Multi.to_list/1` in tests
|
- If you want to validate changesets without hitting the DB → `Multi.to_list/1` in tests
|
||||||
- If operations are simple and static (no dynamic branching) → consider `Repo.transact(fn -> ... end)` instead
|
- If operations are simple and static (no dynamic branching) → consider `Repo.transaction(fn -> ... end)` instead
|
||||||
|
|
||||||
<!-- PATTERN_COMPLETE -->
|
<!-- PATTERN_COMPLETE -->
|
||||||
|
|||||||
Reference in New Issue
Block a user