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:
-
The task name:
- This is the name of the task that will be used to execute it from the command line.
-
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.
- This is a brief description of what the task does, which will be displayed when the user runs the
-
The task implementation:
- This is the code that is executed when the task is run. It is defined in a function called
run/1
.
- This is the code that is executed when the task is run. It is defined in a function called
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.