Current section
Files
Jump to
Current section
Files
lib/tasks/drill.ex
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])
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{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 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