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

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
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
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
jc00ke
Expanding on this topic: https://forum.elixirforum.net/t/map-typespec-question/19217 Let’s say I have a map with required and optional k...
New

Other popular topics Top

lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
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
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

We're in Beta

About us Mission Statement