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">,
# …
.update! either
@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.