Ecto: Repo.update! doesn't set field according to changeset

Created on 14 Dec 2016  Â·  7Comments  Â·  Source: elixir-ecto/ecto

Precheck

  • [x] Do not use the issues tracker for help or support requests (try Stack Overflow, IRC or mailing lists, etc).
  • [x] For proposing a new feature, please start a discussion on elixir-ecto.
  • [x] For bugs, do a quick search and make sure the bug has not yet been reported.
  • [x] Finally, be nice and have fun!

Environment

  • Elixir version (elixir -v): _1.3.4_
  • Database and version (PostgreSQL 9.4, MongoDB 3.2, etc.): _PostgreSQL 9.5.4_
  • Ecto version (mix deps): _2.0.5_
  • Database adapter and version (mix deps): _postgrex 0.12.1_
  • Operating system: _macOS 10.12.2_

Current behaviour

In the down section of an Ecto migration, the goal is to select a Plan and set the user_id on it.
The migration runs, no errors are raised, but the user_id in the database is NULL.

The reduced code is as follows:

  # …
  def down do
    # …
    plan = Repo.get!(Plan, plan_id)

    IO.inspect(plan)

    changeset =
      plan
      |> Ecto.Changeset.change(user_id: 42)

    IO.inspect(changeset)

    changeset
      |> Repo.update!
      |> IO.inspect()
    # …
  end

The inspected output is as follows:

15:07:23.218 [debug] QUERY OK source="plans" db=0.9ms decode=8.7ms
SELECT p0."id", p0."client_id", p0."details", p0."inserted_at", p0."updated_at" FROM "plans" AS p0 WHERE (p0."id" = $1) [17]
%Plan{__meta__: #Ecto.Schema.Metadata<:loaded, "plans">,
 client: #Ecto.Association.NotLoaded<association :client is not loaded>,
 client_id: 1, id: 17, inserted_at: #Ecto.DateTime<2016-11-24 18:06:43>,
 details: […],
 updated_at: #Ecto.DateTime<2016-12-14 19:48:44>}


#Ecto.Changeset<action: nil, changes: %{user_id: 42}, errors: [],
 data: #Plan<>, valid?: true>


%{__meta__: #Ecto.Schema.Metadata<:loaded, "plans">,
  __struct__: Plan,
  client: #Ecto.Association.NotLoaded<association :client is not loaded>,
  client_id: 1, id: 17, inserted_at: #Ecto.DateTime<2016-11-24 18:06:43>,
  details: […],
  updated_at: #Ecto.DateTime<2016-12-14 19:48:44>, 
  user_id: 42}

The struct returned by update! at this point is not a %Plan{} struct anymore, but a "simple" %{} map, that doesn't get persisted.

Using the :force option that update! has

    # …
    changeset
      |> Repo.update!(force: true)
      |> inspector
    # …

The update is forced, but the query confirms that the user_id change in the changeset is lost:

15:27:35.292 [debug] QUERY OK db=0.6ms
UPDATE "plans" SET "updated_at" = $1 WHERE "id" = $2 [{{2016, 12, 14}, {20, 27, 35, 0}}, 17]
%{__meta__: #Ecto.Schema.Metadata<:loaded, "plans">,
  # …

Expected behaviour

.update! either

  • fails, with a meaningful message
  • or updates the Plan according to the changeset that's provided.

All 7 comments

@mariusbutuc I noticed that you don't have user_id on the Plan struct when doing the 1st inspect - there's a client_id there though. Perhaps you're missing user_id on your schema definition?

@wojtekmach you're totally right!

The purpose of this migration is to extract a Client concept out of User, as later there can be different types of Users, with different roles.

So this does change the Plan schema from

  schema "plans" do
    belongs_to :user, User
    embeds_many :details, Detail

    timestamps()
  end

to

  schema "plans" do
    belongs_to :client, Client
    # …
  end

Is there an easy way to do such a rollback, that includes making the original connections in the data? Or, for rollback, we can't change the schema? Is making 2 migrations / 2 commits the way to go for something like this?

First thoughts - don't use schemas in migrations. You're going to have a really bad time when you change your schemas - suddenly every migration is broken! Use the schemaless features of ecto.

Another issue I see is that we should verify that a field passed on change is actually in the schema.

I will expand later.

Another issue I see is that we should verify that a field passed on change is actually in the schema.

Let's open up an issue for it.

Sounds good; closing in favour of #1856 and looking forward to continuing the conversation there.


As for

Use the schemaless features of ecto.

upon a quick search, @josevalim's post on Ecto’s insert_all and schemaless queries came up.

Keeping beginners in mind, is this a good place to learn more about it? Are there other good resources to get up to speed?

@mariusbutuc the Ecto book that article comes can be a good follow up resource from the docs: http://pages.plataformatec.com.br/ebook-whats-new-in-ecto-2-0

@michalmuskala should we include the book in the README / Ecto docs?

I don't see anything wrong with including the book as a resource in the docs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

brandonparsons picture brandonparsons  Â·  3Comments

jonasschmidt picture jonasschmidt  Â·  4Comments

jordi-chacon picture jordi-chacon  Â·  4Comments

atsheehan picture atsheehan  Â·  4Comments

yordis picture yordis  Â·  4Comments