iarekk

iarekk

Hi all, new to Elixir, going through the “Programming Phoenix LiveView” book right now. Fairly new to working with Ecto as well :slight_smile:

An exercise in chapter instructs the reader to add a username field to the User struct, and implement the Ecto migrations to support that.

Additionally, I would like to make the username:

  • mandatory
  • unique
  • populate email as default value for existing DB records.

I’ve achieved it successfully with the following migrations:

First migration: create column:
  def change do
    alter(table(:users)) do
      add :username, :citext
    end
  end
Second migration, fill column and make it unique/mandatory:
  def change do
    from(u in Pento.Accounts.User, update: [set: [username: u.email]])
    |> Pento.Repo.update_all([])

    alter(table(:users)) do
      modify :username, :citext, null: false
    end

    create(unique_index(:users, :username))
  end

However, I can’t seem to be able to make these updates as a single migration. Why?

My first draft looked like this:

defmodule Pento.Repo.Migrations.CreateUsernameColumn do
  use Ecto.Migration
  import Ecto.Query, only: [from: 2]

  def change do
    alter(table(:users)) do
      add :username, :citext
    end

    from(u in Pento.Accounts.User, update: [set: [username: u.email]])
    |> Pento.Repo.update_all([])

    alter(table(:users)) do
      modify :username, :citext, null: false
    end

    create(unique_index(:users, :username))
  end
end

… but it throws an error in the from(u in... statement as Users doesn’t have the column called username. But I just created that column 2 rows above. What am I missing?

P.S. If it helps, complete repo here: Ch2/add username field by iarekk · Pull Request #7 · iarekk/programming_phoenix_liveview · GitHub

Showing Posts 1 to 9

ken-kost

ken-kost

change/0 is a simplification of up and down migration. Note that you want to be able to reverse the migration also. In any case, I’m not sure, but I think change is executed like a single transaction, meaning the first alter table wasn’t actually written until everything under succeeded, which it didn’t because the first alter table wasn’t written yet.
edit: catch-22? :cowboy_hat_face:

sodapopcan

sodapopcan

It very well might be transaction related (everything in change is indeed run inside one). If you ever need to commit a transaction in a migration you can use the flush() function. However, @iarekk, you really shouldn’t put schema names in migrations. If the schemas ever change in a way that older migrations don’t expect, they will break. It’s actually best to keep data migrations complete out of migrations and use a different solution but if you do, they should be written as raw SQL: execute("update users u set username = u.email") and best written as separate migrations anyway so they can be deleted once they’ve been run in production.

LostKobrakai

LostKobrakai

SQL works fine, but for completeness one can write ecto queries without depending on schemas as well. from u in "users", update: [set: [username: u.email]] should do just fine.

iarekk

iarekk OP

Thank you!

flush() works if I write the migration like that:

def change do
    alter(table(:users)) do
      add :username, :citext
    end

    flush()

    from(u in Pento.Accounts.User, update: [set: [username: u.email]])
    |> Pento.Repo.update_all([])

    alter(table(:users)) do
      modify :username, :citext, null: false
    end

    create(unique_index(:users, :username))
  end

Fair point on the schemas - unlike migrations, the schema object exists as the ‘latest’ version. I’ll need to clean up the code – really liking @LostKobrakai’s suggestion there.

On the topic of data updates and migrations, I’m not sure I understand.

AFAIK the following should be true (assume we have migrations m1..mN):

  1. Given an empty server, executing migrations m1..mN should give you a working database.
  2. Given a server with migrations m1..mK already applied, applying mK+1..mN should give you a working database.

Now, if migration k introduces a column that can’t be null, it should also:

  • fill it with some data
  • OR specify a default

So I can’t really see how I could separate ‘schema updates’ from ‘data updates’ unless I sacrifice property 2, and make some mK into dead ends. By dead end I mean, that you have to reset your DB and start from scratch.

LostKobrakai

LostKobrakai

Working database can mean many things. E.g. you can do “deploy:create new column, don’t depend on it”, “backfill new column”, “deploy:start using column”.

sodapopcan

sodapopcan

Unfortunately the usual way around it is multiple deploys, ie, what @LostKobrakai is saying in terms of “don’t depend on it” → “start using column”. It can get super annoying but was just pointing it out as something to think about going forward. It all depends on your production setup too but you can end up in a situation where you’ve backfilled the columns but you still have the old code running on a server that tries to create a new record that doesn’t even know about the new column which would crash. Best case scenario here is that the user gets annoyed and tries again but depending what data you are migrating it could be much worse.

Anyway, it’s a bit beyond the scope of the book likely and if you are still in development this doesn’t matter, I just wanted to point it out.

iarekk

iarekk OP

OH. Right!

My approach until now has been: “Given a list of migrations and a database with schema K and with data, the migrations should bring the database to schema N plus consistent data”

Whereas I could just as easily say “Given an empty database with schema K, the migrations should bring the database to schema N”, and let the particular DB owner perform their own data manipulations.

The former feels way more shiny, but perhaps I’m asking too much from the migrations scripts :smiley:

LostKobrakai

LostKobrakai

You can still run data migrations on your own. The important difference with separated data migrations is that they’re not part of deployments and are therefore less risky. They can take time, the can run in batches, they can run over night, they can be aborted, reset, all those things, which are a hassle while doing a deployment.

iarekk

iarekk OP

Yeah! I didn’t think that through for reasons of:

  1. Having a DBA team
  2. Working with DynamoDB for most of our data needs.

Thank you for your help! I understand things a bit better now :slight_smile:

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews