marick

marick

Interaction between optimistic logging and changeset errors: `lock_version` is updated in error-return changeset

Later: I have realized this applies to validation errors, not constraints, as I’d originally thought. See footnote [3]

I have a changeset with both a constraint and optimistic locking:

    %Animal{id: id}
    |> cast(attrs, [:name, :lock_version])
    |> unique_constraint(:name, name: "unique_available_names")
    |> optimistic_lock(:lock_version)

IF the unique_constraint is violated, I get an {:error, changeset} back, but the changeset contains an incremented version of the :lock_version. Since the update didn’t happen, the version in the changeset will be used to populate the edit form, which uses a hidden input to send back the lock version:

<%= form_for @changeset, ...
  <%= text_input(f, :name) %> ...
  <%= hidden_input(f, :lock_version) %>    <<<<<<<<<<<

A corrected update attempt will now fail with a StaleEntryError.[1]

Am I misunderstanding the right way to use optimistic locking?[2] Details below.

[1] I assume the error check uses equality rather than >=, but I only traced the code to Ecto.Adapters.Sql.schema, which is not far enough.

[2] This seems a different problem than 25570.

[3] I’ve confirmed that this also happens with a validate_length error. That is, the :lock_version is set to 2 in the error changeset, even though the :lock_version in the on-disk copy is 1.

full changeset: #Ecto.Changeset<
  action: :update,
  changes: %{lock_version: 2, name: "preexisting"},   <<<<<<<<<<<<<<
  errors: [
    name: {"should be %{count} character(s)",
     [count: 3838, validation: :length, kind: :is, type: :string]}
  ],
  data: #Crit.Usables.Animal<>,
  valid?: false
>

Start with two animals:

%Crit.Usables.Animal{
  available: true,
  id: 48789,
  lock_version: 1,
  name: "Original Bossie",
}

%Crit.Usables.Animal{
  id: 48790,
  lock_version: 1,
  name: "preexisting",
}

The form will send back these params:

%{"lock_version" => "1", "name" => "preexisting"}

However, the changeset pipeline shown above transforms the lock version before it hits Repo.update:

update: #Ecto.Changeset<
  action: nil,
  changes: %{lock_version: 2, name: "preexisting"},
  errors: [],
  data: #Crit.Usables.Animal<>,
  valid?: true
>

… and the resulting error changeset contains the updated :lock_version:

#Ecto.Changeset<
  action: :update,
  changes: %{lock_version: 2, name: "preexisting"},       <<<<<<<<<<<<<<
  errors: [
    name: {"has already been taken",
     [constraint: :unique, constraint_name: "unique_available_names"]}
  ],
  data: #Crit.Usables.Animal<>,
  valid?: false
>

Might be worth noting that the original Animal (changeset.data) has not been changed.

Where Next?

Popular in Questions Top

_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lists...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New

Other popular topics Top

sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 42920 311
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30877 112
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

We're in Beta

About us Mission Statement