Reading Time: 4 mins

While writing a custom Mix task in Elixir, I ran into minor challenges for running / executing an Ecto query to update a database table column from within my mix task. It was not straightforward initially thus I thought to share here. In this short article, we will also see how to pass arguments to our mix task to run dynamic query.

First, what is Mix? Elixir documentation says:

Mix is a build tool that ships with Elixir that provides tasks for creating, compiling, testing your application, managing its dependencies and much more.

Mix build tool is like rake Ruby utility and make Unix utility but with ecosystem specific differences.

Its pretty common need to extend our Elixir application by adding our own custom Mix tasks. We already use Mix tasks by default in our project when we run mix phoenix.new my_application to create new web application. This mix task has a recipe to generate entire project structure for our application along with necessary packages and utility.

To create our own custom mix task, we just need to add a new file in folder lib/mix/tasks. Say for example, we will create a mix task as lib/mix/tasks/set_all_products_to_active_state.ex to set all products to active state.

# run as => mix UpdateAllProducts.SetActive
defmodule Mix.Tasks.UpdateAllProducts.SetActive do
 use Mix.Task
 alias Store.{Repo, Product}

 @shortdoc "Sets all products to active state"
 def run(_) do
   [:postgrex, :ecto]
     |> Enum.each(&Application.ensure_all_started/1)
   Repo.start_link

   Repo.update_all Product, set: [is_active: true]
   IO.puts "Task completed successfully."
 end
end

In snippet alias Store.{Repo, Product} above .... Store is our project name and Product is one of the model in our project. To run Ecto queries with postgres from within this mix task, we just needed to include following 3 lines.

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

This mix task will set all products to active state, i.e. it will set is_active field to true for all products (in our products table).

To run this mix task, we just need to execute this command mix UpdateAllProducts.SetActive. Run command mix help now and you'll notice that our mix task is also in the list of available tasks with short document we added.

enter image description here

We can also pass true or false as argument to our mix task as follows

# run => mix UpdateAllProducts.SetState true
# run => mix UpdateAllProducts.SetState false
defmodule Mix.Tasks.UpdateAllProducts.SetState do
  use Mix.Task
  alias Store.{Repo, Product}

  @shortdoc "Sets all products to active or inactive state based on argument value."
  def run(args) do
    state = Enum.at(args, 0) |> String.to_existing_atom
    [:postgrex, :ecto]
      |> Enum.each(&Application.ensure_all_started/1)
    Repo.start_link

    Repo.update_all Product, set: [is_active: state]
    IO.puts "Task completed successfully."
  end
end

To run this mix task with argument i.e. state as true, we need to execute this command mix UpdateAllProducts.SetState true

Try this on your terminal mix help new and you'll notice nice documentation by mix help for new command.

Reference & related help on mix tasks:

https://stackoverflow.com/questions/38225406/how-to-get-data-from-ecto-in-a-custom-mix-task
http://nhu313.github.io/elixir/2015/03/22/elixir-mix-task.html