optimistic lock

113 views
Skip to first unread message

Filip Moens

unread,
Apr 10, 2016, 8:29:45 AM4/10/16
to elixir-ecto
Hi all,

I'm trying to use the optimistic lock feature in Phoenix, however it doesn't seem to work as I expected.

The Model (as you can see, I changed use Demo.Web, :model to use Ecto.Schema as I think this is the newer way of doing things - not sure though) :

defmodule Demo.Post do
#  use Demo.Web, :model
  use Ecto.Schema

  schema
"posts" do
    field :title, :string
    field :lock_version, :integer, default: 1

    timestamps
 
end

  @required_fields ~w(title lock_version)
 
@optional_fields ~w()

 
def changeset(model, params \\ :empty) do
    model
   
|> Ecto.Changeset.cast(params, @required_fields, @optional_fields)
   
|> Ecto.Changeset.optimistic_lock(:lock_version)
 
end
end


The update method in the controller (as generated by Phoenix running "mix phoenix.gen.html Post posts title:string lock_version:integer":

def update(conn, %{"id" => id, "post" => post_params}) do
  post = Repo.get!(Post, id)
  changeset
= Post.changeset(post, post_params)

  IO.inspect changeset

  case Repo.update(changeset) do
    {:ok, post} ->
      conn
     
|> put_flash(:info, "Post updated successfully.")
     
|> redirect(to: post_path(conn, :show, post))
   
{:error, changeset} ->
      render
(conn, "edit.html", post: post, changeset: changeset)
 
end
end

Now, I open 2 different tabs in my browser to go to the edit page of a specific post (current lock_version: 2).
I modify it in tab 1 and click "Submit". The lock_version is updated to 3.
I modify it in tab 2 and click "Submit". I expected an Ecto.StaleModelError, but that didn't happen. The lock version is updated to 4.

Here is the inspect log of the changeset in the controller:

%Ecto.Changeset{action: nil,
 changes: %{lock_version: 4, title: "post a modified again"}, constraints: [],
 errors: [], filters: %{lock_version: 3},
 model: %Demo.Post{__meta__: #Ecto.Schema.Metadata<:loaded>, id: 5,
  inserted_at: #Ecto.DateTime<2016-04-10T12:21:14Z>, lock_version: 3,
  title: "post a modified", updated_at: #Ecto.DateTime<2016-04-10T12:22:10Z>},
 optional: [], opts: [],
 params: %{"lock_version" => "2", "title" => "post a modified again"},
 prepare: [], repo: nil, required: [:title, :lock_version],
 types: %{id: :id, inserted_at: Ecto.DateTime, lock_version: :integer,
   title: :string, updated_at: Ecto.DateTime}, valid?: true, validations: []}

 
As you can see, the lock_version in params is 2.
The lock_version in changes however is 4. 

I suspect this problem (if it is one) has something to do with the Repo.get! call in the controller.

What am I missing / not understanding / doing wrong?

Filip

José Valim

unread,
Apr 10, 2016, 8:39:01 AM4/10/16
to elixi...@googlegroups.com
You are right, it is a bug in Ecto. It has been fixed in both v1.1 and master branches.



José Valim
Skype: jv.ptec
Founder and Director of R&D

--
You received this message because you are subscribed to the Google Groups "elixir-ecto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-ecto...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-ecto/8bb09faf-1c3c-40cf-8a2c-a335c35ee687%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Filip Moens

unread,
Apr 10, 2016, 10:10:50 AM4/10/16
to elixir-ecto, jose....@plataformatec.com.br
Ok, great José.

Not sure if this related: when creating a Post.

In the model: 

field :lock_version, :integer, default: 1

In the Post controller:

def create(conn, %{"post" => post_params}) do
  changeset = Post.changeset(%Post{}, post_params)

  IO
.inspect changeset

 
case Repo.insert(changeset) do
    {:ok, _post} ->
      conn
     
|> put_flash(:info, "Post created successfully.")
     
|> redirect(to: post_path(conn, :index))
   
{:error, changeset} ->
      render
(conn, "new.html", changeset: changeset)
 
end
end


After the create I have a Post with lock_version 2 (where I expected 1)

Changeset log:

[info] POST /posts
[debug] Processing by Demo.PostController.create/2
  Parameters: %{"_csrf_token" => "bg8ddS8eHFpUHWMcJm96AzkpXBUUNgAA7Nd9CL7b9JTnG+/QJN/rfw==", "_utf8" => "✓", "post" => %{"title" => "post x"}}
  Pipelines: [:browser]
%Ecto.Changeset{action: nil, changes: %{lock_version: 2, title: "post x"},
 constraints: [], errors: [], filters: %{lock_version: 1},
 model: %Demo.Post{__meta__: #Ecto.Schema.Metadata<:built>, id: nil,
  inserted_at: nil, lock_version: 1, title: nil, updated_at: nil},
 optional: [:lock_version], opts: [], params: %{"title" => "post x"},
 prepare: [], repo: nil, required: [:title],
 types: %{id: :id, inserted_at: Ecto.DateTime, lock_version: :integer,
   title: :string, updated_at: Ecto.DateTime}, valid?: true, validations: []}
[debug] INSERT INTO "posts" ("inserted_at", "updated_at", "lock_version", "title") VALUES ($1, $2, $3, $4) RETURNING "id" [{{2016, 4, 10}, {14, 3, 28, 0}}, {{2016, 4, 10}, {14, 3, 28, 0}}, 2, "post x"] OK query=0.9ms

If I specify in the Model:

field :lock_version, :integer, default: 0

I end up with a lock_version of 1, which is better.
But, in this case, the meaning of "default: 0" for the lock version in the Model seems "odd".

I would expect a default value for the lock_version always to be used on Create, so default: 0 -> 0 after initial Create, or default: 1 -> 1 after initial Create.
As it currently works, things are kind of confusing.

Filip

José Valim

unread,
Apr 10, 2016, 10:13:11 AM4/10/16
to elixi...@googlegroups.com
All operations will increase the lock. If you don't want to bump it on create, then don't call the optimistic_lock for the create changeset. :)



José Valim
Skype: jv.ptec
Founder and Director of R&D

Filip Moens

unread,
Apr 10, 2016, 10:24:12 AM4/10/16
to elixir-ecto, jose....@plataformatec.com.br

Goodness, I'm stepping away from my computer for a few hours now - feeling like an idiot :-)
Reply all
Reply to author
Forward
0 new messages