Mix Release. Run seeds.exs file via release binary

Hello,

When one is within the Mix context it’s possible to run

$ mix run priv/repo/seeds.exs

Which will evaluate code like the following into the DB:

#/priv/repo/seeds.exs ---> copied to _build/rel/appname/prod/seeds.exs  once release is built
defmodule Seeder do
  def start_items do
      %Item{}
      |> Item.changeset(%{name: Faker.Commerce.product_name()})
      |> Repo.insert!()
  do
end

Seeder.start_items()

I would like to also run the same script in production from a mix release binary à la

./bin/appname eval "Appname.Release.migrate"

What is the recommended way? I already copied the seeds.exs to overlays so they are located in here

_build/rel/appname/prod/seeds.exs 

Thank you!

1 Like

/some_release/bin/prod eval "MyApp.Seeder.start_items" :man_shrugging: assuming you are using releases

1 Like

Oh, so you suggest to have the Seeder module out of rel/overlays/seeds.exs and instead make it into a module inside /lib/appname/seeder.ex, that sounds like a different approach but a good idea nevertheless :slightly_smiling_face:

Much like the example in the docs for releases and migrations, I would just follow that pattern.

I mean it could even just go in the Appname.Release module

And just for the record I’m talking about Invoking priv/repo/seeds.exs from that location. Not defining the seeds.exs in there

Following Deploying with Releases — Phoenix v1.7.10 for migrations I got stuck when it comes to the seeds due to the fact that they by consensus live in a script .exs which I assumed requires the mix tool to the mix runex.

Ah cool. Even better :slight_smile: Can you briefly annotate how that invoking would look like, I did find its full path via priv_dir(:appname) function but didn’t figure out how to run the script itself.

defmodule Appname.Release do
  def run_seeds do
      # Load seeds.exs script here
  end
end
1 Like

Good question, I’ve never tried to load a exs from inside elixir before. I"m sure it’s simple to do.

Code.eval_file/1 will execute an EXS file. That is how I am running migrations via a mix release.

8 Likes

What’s wrong with Ecto.Migrator.up/4?

Its for running the seeds.exs
Edit: oh yeah for the migration. Meh to each their own.

To seed one might pack seeds into a migration and run a migration. There is no mix by default on target release. Of course, another way [not recommended] would be to include mix app in the release.

I currently use the following in one of my projects:

defmodule MyApp.Release.Seeder do
  @moduledoc """
  Release tasks for seeds.
  """

  require Logger

  @app :my_app

  @doc """
  Seed seeds file for repo.
  """
  @spec seed(Ecto.Repo.t(), String.t()) :: :ok | {:error, any()}
  def seed(repo, filename) do
    load_app()

    case Ecto.Migrator.with_repo(repo, &eval_seed(&1, filename)) do
      {:ok, {:ok, _fun_return}, _apps} ->
        :ok

      {:ok, {:error, reason}, _apps} ->
        Logger.error(reason)
        {:error, reason}

      {:error, term} ->
        IO.warn(term, [])
        {:error, term}
    end
  end

  @spec eval_seed(Ecto.Repo.t(), String.t()) :: any()
  defp eval_seed(repo, filename) do
    seeds_file = get_path(repo, "seeds", filename)

    if File.regular?(seeds_file) do
      {:ok, Code.eval_file(seeds_file)}
    else
      {:error, "Seeds file not found."}
    end
  end

  @spec get_path(Ecto.Repo.t(), String.t(), String.t()) :: String.t()
  defp get_path(repo, directory, filename) do
    priv_dir = "#{:code.priv_dir(@app)}"

    repo_underscore =
      repo
      |> Module.split()
      |> List.last()
      |> Macro.underscore()

    Path.join([priv_dir, repo_underscore, directory, filename])
  end

  @spec load_app() :: :ok | {:error, term()}
  defp load_app(), do: Application.load(@app)
end

And then for the release:

  • bin/my_app eval 'MyApp.Release.Seeder.seed(Elixir.MyApp.Repo, "seed.exs")

PS: You probably need to add to your config.exs

config :my_app, MyApp.Repo,
  start_apps_before_migration: [:logger]
10 Likes

Thanks @polygonpusher @webuhu @mudasobwa I learned from all insights and comments!

Defly @dbell’s answers the original question most straightforwardly though, thx! :slight_smile:

1 Like

What about when one needs to get information from a legacy DB? Code.Eval_file seems to forget about the application variables.

What do you mean by legacy DB?
I think you can pass Elixir.MyApp.LegacyRepo also.?

But of course, I also think Elixir application variables wont be available in the Code.eval_file/1 evaluated seed.exs.