Create mix tasks that invoke existing modules which use Ecto

I have a simple module that, when invoked, lists all records in a table.

defmodule App.Adapter do
  alias App.Repo

  def list_all(model) do
    Repo.all(model)
  end
end

I want to have a simple mix task such that I can invoke this function.

defmodule Mix.Tasks.ListAllThings do
  use Mix.Task

  def run(_) do
    App.Adapter.list_all(App.Thing)
  end
end

If I try to invoke this task (and the task does exist), the error is (RuntimeError) could not lookup App.Repo because it was not started or it does not exist. I have a supervisor and am able to run these queries from iex -S mix in the console. Everything I have looked up about this so far has suggested using import Mix.Ecto but none of the helper method seem to behave to me (#ensure_started doesn’t make sense to me).

This list_all task is meant as a quick proof of concept for a small app that essentially just acts as a bundle of scripts to interface with a DB on the fly (no API involved and the app does not persist on a machine for any period of time beyond running the script). Am I just tangling myself in an anti-pattern? If I want to just write elixir scripts to interface with a DB should I be approaching this with something other than tasks?

I’m still curious about this but having hacked at this a bit it seems like using mix run to run scripts is a more felicitous approach for my use case.

You need to make sure to start Repo before using it in a Mix task:

def run(_) do
 Mix.Ecto.ensure_started(App.Repo, [])
 App.Adapter.list_all(App.Thing)
end

WARNING: In Ecto 2.X you should not reference Mix.Ecto because it is a private API: https://github.com/elixir-ecto/ecto/issues/1586#issuecomment-233301683

And in Ecto 3.X, while Mix.Ecto is now considered public (i.e. no longer has @moduledoc false), the ensure_started function has been removed. Off the top of my head I’m not sure what you should use instead (but maybe it is Mix.Task.run).

1 Like

Running into this issue myself. Currently creating a mix task to generate a shell of my graphql schema based on my ecto schema. Need to get info about indexed columns/fields as well as nullability of fields, so attempting to execute:

Ecto.Adapters.SQL.query!(MyApp.Repo, "select * from pg_indexes where schemaname='public'")

for example to get a list of indexes. I encounter the same error:

** (RuntimeError) could not lookup MyApp.Repo because it was not started or it does not exist
    lib/ecto/repo/registry.ex:18: Ecto.Repo.Registry.lookup/1
    lib/ecto/adapter.ex:127: Ecto.Adapter.lookup_meta/1

Mix.Ecto.ensure_started... No longer exists. How else can we start the repo in a mix task?

"ecto": {:hex, :ecto, "3.1.7",},

The Phoenix docs suggest using:

Mix.Task.run("app.start")

in your task’s run/1 function to boot the application and its dependencies.

Alternatively, if you just need a repo, there’s Ecto.Migrator.with_repo/2: https://github.com/elixir-ecto/ecto_sql/blob/master/lib/ecto/migrator.ex#L119

2 Likes

Thanks @al2o3cr, Ecto.Migrator.with_repo/2 was just what the doctor ordered. Works! Wish I could select as answered.