How to get data from Ecto in a custom mix task
Asked Answered
G

4

12

I want to display data from my DB through Ecto in a custom mix task. How can I get the Ecto repo in my task (or start it)?

I tried something like this but it didn't work:

defmodule Mix.Tasks.Users.List do


use Mix.Task
  use Mix.Config
  use Ecto.Repo, otp_app: :app

  @shortdoc "List active users"
  @moduledoc """
    List active users
  """
  def run(_) do
    import Ecto.Query, only: [from: 1]

    Mix.shell.info "=== Active users ==="
    query = from u in "users"
    sync = all(query)
    Enum.each(users, fn(s) -> IO.puts(u.name) end)
  end

end

This will give me the following output when I launch mix users.list:

** (ArgumentError) repo Mix.Tasks.Users.List is not started, please ensure it is part of your supervision tree
    lib/ecto/query/planner.ex:64: Ecto.Query.Planner.query_lookup/5
    lib/ecto/query/planner.ex:48: Ecto.Query.Planner.query_with_cache/6
    lib/ecto/repo/queryable.ex:119: Ecto.Repo.Queryable.execute/5

Any idea or other way to solve this problem?

Girand answered 6/7, 2016 at 13:36 Comment(0)
L
14

Ecto 3.x:

ensure_started has since been removed from Ecto. There has been a lot of confusion around this topic. See here https://github.com/elixir-ecto/ecto/pull/2829#issuecomment-456313417 for more. José suggests to either start the app using Mix.Task.run "app.start" or run the repo using MyApp.Repo.start_link(...).

Ecto 2.x:

This used to work in 2.x, but apparently Mix.Ecto was not considered part of the public API.

There is actually a helper module Mix.Ecto (https://github.com/elixir-ecto/ecto/blob/master/lib/mix/ecto.ex) that makes it easier to write mix tasks that use ecto:

defmodule Mix.Tasks.Users.List do
  use Mix.Task
  import Mix.Ecto

  def run(args) do
    repos = parse_repo(args)

    Enum.each repos, fn repo ->
      Mix.shell.info "=== Active users ==="

      ensure_repo(repo, args)
      ensure_started(repo, [])
      users = repo.all(Ectotask.User)

      Enum.each(users, fn(s) -> IO.puts(s.name) end)
    end
  end
end

This helper gives you access to parse_repo/1, ensure_repo/2, ensure_started/1. parse_repo will let your task fit in nicely with other ecto mix tasks, for example it will let you pass -r to specify a different repo.

➤ mix users.list
=== Active users ===
Adam
➤ mix users.list -r Ectotask.Repo22
=== Active users ===
** (Mix) could not load Ectotask.Repo22, error: :nofile. Please pass a repo with the -r option.

ensure_started makes sure the repo is running, which you were lacking.

For guidance and inspiration, you can look at how other ecto mix tasks are implemented at https://github.com/elixir-ecto/ecto/tree/master/lib/mix/tasks

Lavine answered 6/7, 2016 at 14:24 Comment(4)
Thank you. This was what I was missing ! !Girand
The arity of ensure_started has been changed from 1 to 2 since Ecto 2.0. The second argument is a keyword list. We should change the line ensure_started(repo) to ensure_started(repo, []).Phyllida
WARNING: You should not import Mix.Ecto because it is a private API and is now broken in Ecto 3.0: github.com/elixir-ecto/ecto/issues/1586#issuecomment-233301683Creel
@JasonAxelson thanks for pointing this out! I have updated my answer to address ecto 3.0Lavine
D
12

As addition to Jason Harrelson's answer: it's also necessary to start Postgrex and Ecto.

[:postgrex, :ecto]
|> Enum.each(&Application.ensure_all_started/1)

MyApp.Repo.start_link

UPDATE:

Another approach is to use mix task to start application:

Mix.Task.run "app.start", []
Doubtless answered 11/11, 2016 at 6:51 Comment(1)
I think your original approach is more explicit, and therefore more correct, even though it takes slightly more code.Rank
L
2

You need to ensure the repo is started prior to using it

MyApp.Repo.start_link
Logway answered 6/7, 2016 at 14:5 Comment(0)
G
0

I also found another solution when working with Phoenix. I created a new file in priv/repo with :

defmodule Users.List do
  def run() do
    Mix.shell.info "=== Active users ==="

    users = App.Repo.all(App.User)
    Enum.each(users, fn(s) ->
      Mix.shell.info("#{s.name}")
    end)
  end
end
Users.List.run

Then I run it with mix run priv/repo/users.list.exs from my project root.

Girand answered 6/7, 2016 at 15:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.