Ecto Stale Entry Error – Solving This Cryptic Elixir Error

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 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. When I’m not writing these blog posts, I’m a freelance Elixir and Ruby developer and working on Calculate, a product which makes it easier for you to create software estimates for your projects. Feel free to leave a comment if you have any questions.

You can also follow me on the Twitters at: @DeLongShot

  • Fey Martynov

    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!