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" 4. Lastly, you can override the path of the seeds directory by passing the --seeds-path option: $ mix drill -r MyApp.Repo --seeds-path priv/custom_repo/seeds """ @shortdoc "Seeding task" @switches [seeds_path: :string] use Mix.Task import Mix.Ecto alias Drill.Seeder alias Drill.Utils @impl Mix.Task def run(args) do repo = parse_repo(args) |> hd() ensure_repo(repo, []) {opts, _} = OptionParser.parse!(args, switches: @switches) opts = opts |> Keyword.put(:task_timeout, Application.get_env(:drill, :timeout, 600_000)) |> Keyword.put_new( :seeds_path, Seeder.seeders_path(repo, Application.get_env(:drill, :directory, "seeds")) ) # Start the app Mix.Task.run("app.start", args) # Run the seed seed(repo, opts) end defp seed(repo, opts) do seeder_modules = Seeder.list_seeder_modules(opts[:seeds_path]) ensure_deps_exists!(seeder_modules) Mix.shell().info("Arranging modules by dependencies") seeder_modules = Utils.sort_seeders_by_deps(seeder_modules) Task.async(fn -> Enum.reduce(seeder_modules, %Drill.Context{repo: repo}, fn seeder, ctx -> Mix.shell().info("#{seeder} started") key = seeder.context_key() constraints = seeder.constraints() on_conflict = seeder.on_conflict() source = seeder.schema() returning = seeder.returning() autogenerated_fields = Seeder.autogenerate_fields(source) seeds = seeder.run(ctx) manual_seeds = Seeder.filter_manual_seeds(seeds) entries = seeds |> Seeder.build_entries_from_seeds() |> Utils.merge_autogenerated_fields_to_entries(autogenerated_fields) {_, result} = insert_all(repo, source, entries, constraints, on_conflict, returning) seeds = Map.put(ctx.seeds, key, manual_seeds ++ result) Mix.shell().info("#{seeder} finished") %{ctx | seeds: seeds} end) end) |> Task.await(opts[:task_timeout]) Mix.shell().info("Drill seeded successfully") end defp insert_all(repo, source, entries, [], _, returning) do repo.insert_all(source, entries, returning: returning) end defp insert_all(repo, source, entries, constraints, on_conflict, returning) do repo.insert_all(source, entries, on_conflict: on_conflict, returning: returning, conflict_target: constraints ) end defp ensure_deps_exists!(seeders) do seeder_nonexisting_deps_map = seeders |> Enum.map(fn seeder -> deps = seeder.deps() {seeder, Enum.filter(deps, &(&1 not in seeders))} end) |> Map.new() any_non_existing_deps? = Enum.any?(seeder_nonexisting_deps_map, &(elem(&1, 1) !== [])) if any_non_existing_deps? do seeder_nonexisting_deps_map |> Enum.filter(&(elem(&1, 1) !== [])) |> Enum.each(fn {seeder, non_existing_deps} -> Mix.shell().error("#{seeder} dependencies cannot be found: #{inspect(non_existing_deps)}") end) raise("Drill failed to run") end end end