Ins and Outs of Elixir Mix Tasks


1. Overview:

One of the many benefits of Elixir is its built-in build tool, Mix. Mix provides a simple way to manage dependencies, compile code, run tests, and execute tasks. In this blog post, we will explore the ins and outs of Elixir Mix tasks and how to use them to improve your Elixir development workflow.

2. What are Elixir Mix Tasks?

Elixir Mix tasks are commands that are executed from the command line using the Mix tool. These tasks can be used for a variety of purposes, such as compiling code, running tests, generating documentation, and more. Elixir comes with several built-in Mix tasks, but developers can also create their own custom tasks to suit their specific needs.

The Anatomy of a Mix Task

Every Mix task has a specific structure that consists of three parts:

  1. The task name:
    • This is the name of the task that will be used to execute it from the command line.
  2. The task description:
    • This is a brief description of what the task does, which will be displayed when the user runs the mix help task_name command.
  3. The task implementation:
    • This is the code that is executed when the task is run. It is defined in a function called run/1.

3. Where to put them?

Mix tasks are usually created under a mix/tasks directory, they follow the same name syntax as Elixir modules e.g. Mix.Tasks.PopulateData. To later run the task you would need to call mix populate_data.

4. Creating Custom Mix Tasks

One of the great things about Mix is that developers can create their own custom tasks to automate repetitive tasks or add functionality to their Elixir projects. To create a custom Mix task, you can use the mix new command to generate a new Elixir project and add a new module to the lib/mix/tasks directory.

Something to consider when creating mix tasks is "what should the task have access to in order for it to execute successfully".

Usually people would add this piece of code in the run/1 function body:

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

which would start the project application and its dependencies.

In some cases it might make more sense to just start the applications needed for the task and nothing else. A real world example of this would be whenever you need access to the app behind Ecto.Repo but want to avoid any background workers from running. In this case the above piece of code could be replaced with:

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

5. Example Mix Task

Considering the above points we will create a mix task that will be responsible for populating a field called timezone in a table called resources.

defmodule Mix.Tasks.PopulateResourceTimezone do
  @moduledoc """
  The task will find all resource records with missing timezone
  and will populate it.
  """
  use Mix.Task

  import Ecto.Query, warn: false

  require Logger

  alias Project.Repo

  def run(_) do
    applications_started? =
      [:postgrex, :ecto]
      |> Enum.map(&Application.ensure_all_started/1)
      |> Enum.all?(&(elem(&1, 0) == :ok))

    if applications_started? do
      Logger.info("All required applications started successfully.")

      Repo.start_link()

      from(r in "resources")
      |> where([r], is_nil(r.timezone))
      |> Repo.update_all(set: [timezone: "timezone"])

      Logger.info("Task for Resource Timezone population completed successfully.")
    else
      Logger.error("Failed to start all required applications.")
    end
  end
end

A task like this can be used when you have an expensive database operation and running it in a migration might potentially slow down the deploy of the project. To run the mix task we just created we would need to execute the mix populate_resource_timezone command.

Need help?

Book a 1h session with an expert on this very matter

€75/h

Pair programming

Pair programming is an agile software development technique in which two programmers work together at one workstation. One, the driver, writes code while the other, the observer or navigator,[1] reviews each line of code as it is typed in. The two programmers switch roles frequently.