fireproofsocks

fireproofsocks

Seeds as migrations?

While working through a Docker build/deployment, it hit me that seeds are weird. Apart from requiring some extra setup (e.g. some variant of https://hexdocs.pm/distillery/guides/running_migrations.html), you can wind up in trouble if you accidentally run the seeds when you didn’t expect.

In some cases, the problems with seeds could be solved if they were treated as a migration – I even saw some Ruby Gems that do this, so other people have thought of this.

What are the thoughts about putting seeds into a migration? I don’t think there needs to be any consideration to rolling back a migration like that… the table structure or the table itself would just get altered or deleted.

Most Liked

shanesveller

shanesveller

Are you using Docker-Compose for your production deployments?

I’d probably use docker-compose run --rm myapp migrate and docker-compose run --rm myapp seed after setting up the ReleaseTasks module per the document you linked, plus the two migrate and seed Distillery commands. Obviously, this doesn’t automate the execution of the migrations/seeds as you bring up the application if you’re only typing docker-compose up [-d], but it’s a simple, documentable step that doesn’t leave you with the second container lingering.

If you are running docker-compose on your production deployment targets, hopefully you’re wrapping it with a Systemd unit or something, where you could add an ExecStartPost or similar hook.

I keep a skeleton repo to be a companion to blog posts and other open demonstrations, which has these implemented if you’d like to see a concrete example:

shanesveller

shanesveller

Personally, I also have to fight this attitude/approach at work and it’s a hill I’m willing to die on: this is an abuse of the migrations system. Just use a seeds file, or preferably a more robust vanilla-Elixir module that calls either raw Ecto or Context functions. Expend the modicum of effort to make sure they’re idempotent, and they’re just as safe to run and rerun as mix ecto.migrate can be.

Table/column/index definitions aren’t the same thing as prepopulated data and they shouldn’t be managed the same way. Data insertions in migrations should be minimal, and any data updates done in a migration should be as necessary due to column changes, and execute as quick as possible. More general data munging and sanitization are an operational task that can and should happen out of band.

The only benefit you get from using migrations for this is quick proof that they have been run in the past, and a shortcut to preventing them from running again. Proper idempotence is far more preferable.

You can’t really undo a single migration from history, without just creating an inverse migration, but you can easily clean up rows that are no longer necessary through normal means.

What happens as the data you seed evolves with the application? Do you really want a new migration every time a value changes or an attribute gets added? We should never be editing existing migration files that have been broadly applied. If you adhere to that, is there actual value being captured in all of the prior historical migrations, that aren’t already captured by your VCS data? It’s just cruft, as we often only care about the current/latest state of that info.

Eventually you’ll likely run into the scenario where not every environment gets all the same bits of prepopulated data, and the migrations system is going to be inflexible enough to make this feel pretty clunky.

fireproofsocks

fireproofsocks

We can’t simply use a seeds file because don’t know all the vendors beforehand. We might onboard new ones every couple weeks. So our options are:

Option One:

  1. Push out new code (a new module dedicated to handling the vendor-specific logic).
  2. Manually log in to our admin portal and create a new vendor record with the proper slug ID (so that the dynamic dispatch can resolve correctly to the new vendor module) OR have shell access to the server to run a prepared seed file.

Pros: no migration required
Cons: manual, prone to human errors (misspellings etc) OR it requires shell access to server.

Option Two:

  1. Push out new code (a new module dedicated to handling the vendor-specific logic).
  2. Include a migration that adds the proper record to the vendors table.
    Pros: automated, exact.
    Cons: ???

Keep in mind that in this simplified example, the “vendors” table is very narrow: it’s almost just a key/value store.


Enums

Another use case where “seed migrations” make sense: we have stopped using enums in our database columns because they don’t play nice with migrations. Some divisions “solve” this problem by letting the application restrict the column values. That causes headaches for BI because when they are doing any reporting on those databases, they have to crack open the application code to know what the allowable values are, e.g. status_id 1 means “active” and status_id 13 means “cancelled” etc. And god forbid you accidentally let a bad value in. It’s very opaque, there are no constraints inside the database, and you experience something akin to “vendor lock” with your application code so that even if you wanted to replace the application that sits on top of that database, you’d be stuck coding in the same ad-hoc ersatz enums somewhere inside the application. Boo.

So instead of using enums in the database, we create a dedicated table, e.g. “statuses” and then reference it via a foreign key (I know, it’s hardly revolutionary). It’s more work to set up, but it makes things more transparent for BI/reporting and it plays nice with migrations.

And that seems like a really good use-case for using a migration instead of a seed: the application code has specific logic built around those statuses. The application falls apart if you don’t have specific values in your database. And importantly, when you need to add a new status along with new behavior, a migration seems like a really clean way to introduce that change. Importantly if the enum columns played nice with migrations, then you’d modify the allowed values via a migration anyhow.

If you think of migrations as strictly defining database structure, that solution might annoy you, but if you think of migrations as managed database changes, it seems like a pretty viable solution.

axelson

axelson

Scenic Core Team

What I’m advocating for (in this specific “vendors” table case) is Option 3:

  1. Push out new code
  2. Include an update to the seeds file that adds the new vendor
    Pros: automated, exact
    Note: seeds file runs on every deploy that changes it in the same way that you run migrations on every deploy that change/add any

For enums, we use Postgres enums at work and I’m totally in agreement about adding new values to the enums in a migration.

Also I want to be clear that (unlike shane) I’m not advocating to never “update data” in a migration, just to prefer using seeds instead of migrations for data.

axelson

axelson

Scenic Core Team

Yes exactly that’s what shane and I meant about making the seeds file idempotent. For example by checking if the specific vendor_id already exists in the database.

Where Next?

Popular in Questions Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
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
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
gonzofish
I’m currently trying to understand how to join three tables using Ecto. All the examples I’ve seen use 2, so maybe I’m just missing somet...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
wernerlaude
In AR this is so simple @articles = current_user.articles How to do in Ecto? def index(conn, _params) do current_user = conn.assig...
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

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
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
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
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
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New

We're in Beta

About us Mission Statement