The Ecto stale entry error (Ecto.StaleEntryError) is one of the more cryptic errors you’ll see in Elixir.
Elixir is pretty good about providing helpful error messages and information, but this one left me scratching my head.
Until I figured out one simple cause of the Ecto stale entry error.
Ecto Update
If you are using Ecto’s update/2
callback, you may have come across this error.
To get to the point, you may be seeing the Ecto.StaleEntryError because you are simply trying to update a record that does not exist.
I said it was simple right?
Let’s look at an example:
defmoudle Example.Repo do
use Ecto.Repo, otp_app: :example
end
defmodule Example.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field(:deactivated, :boolean)
end
def changeset(user, params = %{}) do
cast(user, params, [:deactivated])
end
def deactivate(user_id) do
%__MODULE__{id: user_id}
|> changeset(%{deactivated: true})
|> Example.Repo.update() # StaleEntryError here
end
end
defmodule Example.Deactivate do
alias Example.User
def deactivate(user_id) do
User.deactivate(user_id)
# do some other clean-up
end
end
In this example, we have a module, a User
and we want to deactivate an existing user in the database.
Now, you may notice that I’ve cheated a bit.
Instead of querying the database first to see if the user exists, I’m simply creating a User
struct with the id of the user that needs to be deactivated.
def deactivate(user_id) do
%__MODULE__{id: user_id}
|> changeset(%{deactivated: true})
|> Example.Repo.update() # StaleEntryError here
end
The benefit of this approach is that it does not require an extra database call to find the row in the database.
The downside, however, is if I pass a user_id
of a row does not exist, update with throw the Ecto.StaleEntryError
.
How to Prevent Ecto’s Ecto.StaleEntryError
You can easily prevent this error from happening in several ways, which way you choose is completely up to you.
You can wrap the Deactivation.deactivate/1
function in a OTP-compliant process and simply “let it crash”, then let a supervisor restart the process.
If you want to ensure that the record really is there, you can query for the record first, using something like Repo.get/3, then pass the record to update, as the documentation for update shows.
Finally, you could wrap the function in a try/rescue
block, but I would not recommend this one, as the other methods allow for more intentional programming and leverage some of the strengths of Elixir better.
Hopefully, these options will help you save a little time when trying to solve the Ecto stale entry error.
This post is part of my series of Elixir Tutorials
Hey, I’m Adam. I’m guessing you just read this post from somewhere on the interwebs. Hope you enjoyed it.
You can also follow me on the Twitters at: @DeLongShot
If you don’t want an extra database call you can also use:
“`
import Ecto.Query
def deactivate(user_id) do
query = __MODULE__ |> where(id: user_id)
case Example.Repo.update_all(query, set: [deactivated: true]) do
{1, _} -> :ok
{0, _} -> {:error, :user_not_found}
end
end
“`
Ah, yes, forgot about using `update_all` as an option. Good suggestion. Thanks for sharing!