defmodule Mix.Tasks.Drill do @moduledoc """ Runs all seeder modules under "priv/YOUR_REPO/seeds. Needs the Repo as an argument. $ mix drill -r MyApp.Repo Setting seeds directory is similar to [Ecto migration](https://hexdocs.pm/ecto_sql/Mix.Tasks.Ecto.Migrate.html), 1. "YOUR_REPO" is the last segment in your repository name. E.g. MyApp.MyRepo will use "priv/my_repo/seeds". 2. You can configure a repository to use another directory by specifying the :priv key under the repository configuration. The "seeds" part will be automatically appended to it. For instance, to use "priv/custom_repo/seeds": config :my_app, MyApp.Repo, priv: "priv/custom_repo" 3. You can also set the directory by adding the following to your config: config :drill, :directory, "seeds" """ @shortdoc "Seeding task" use Mix.Task import Mix.Ecto alias Drill.Seeder alias Drill.Utils alias Ecto.Migrator @impl Mix.Task def run(args) do repo = parse_repo(args) |> hd() ensure_repo(repo, []) Migrator.with_repo(repo, fn repo -> Migrator.run(repo, :up, all: true) seed(repo) end) end def seed(repo) do otp_app = Application.get_env(:drill, :otp_app) if otp_app, do: IO.warn("Setting otp_app is deprecated. It will now be inferred from the repo.") task_timeout = Application.get_env(:drill, :timeout, 600_000) seed_dir = Application.get_env(:drill, :directory, "seeds") seeder_modules = Seeder.list_seeder_modules(repo, seed_dir) Mix.shell().info("Arranging modules by dependencies") seeder_modules = seeder_modules |> Utils.sort_seeders_by_deps() |> warn_not_able_to_run_seeders(seeder_modules) Task.async(fn -> Enum.reduce(seeder_modules, %Drill.Context{}, fn seeder, ctx -> Mix.shell().info("#{seeder} started") entries = Drill.build_entries(seeder, ctx) key = seeder.context_key() constraints = seeder.constraints() on_conflict = seeder.on_conflict() source = seeder.schema() autogenerated_fields = seeder.autogenerate() entries = Utils.merge_autogenerated_fields_to_entries(autogenerated_fields, entries) {_, result} = insert_all(repo, source, entries, constraints, on_conflict) seeds = Map.put(ctx.seeds, key, result) Mix.shell().info("#{seeder} finished") %{ctx | seeds: seeds} end) end) |> Task.await(task_timeout) Mix.shell().info("Drill seeded successfully") end defp insert_all(repo, source, entries, [], _) do repo.insert_all(source, entries, returning: true) end defp insert_all(repo, source, entries, constraints, on_conflict) do repo.insert_all(source, entries, on_conflict: on_conflict, returning: true, conflict_target: constraints ) end defp warn_not_able_to_run_seeders(arranged_seeders, all_seeders) do unmatched_seeders = all_seeders -- arranged_seeders if Enum.empty?(unmatched_seeders) do arranged_seeders else Mix.shell().info( "Unable to run seeders due to deps not found: #{inspect(unmatched_seeders)}" ) arranged_seeders end end end