# Writing Mix Task in Elixir Phoenix

Imagine you are working on an application that has multiple models (Tables) and each table has some columns. Your manager came up with a requirement to add a new column in one table, say `User` table. The new column you have asked to add is `user_type` of `string` type. You will write the migration to add the new column and as per the new column, you added this new field in form as well. Everything works fine, but what about the records that already exist in the database on `Production` or another environment. That might can give unexpected results. The *solution* to this problem is `task` in Phoenix. It is somewhat inspired by the rails `rake task`. But the question arises we can directly run SQL queries on the database as soon as we run the migration before deploying other changes that are going to change new data. Why there was a need to come with a new concept like `task` to change the data.
There are few benefits of using the Elixir task:
- `Task` code is nothing but pure Elixir code with which you can use to write Ecto Queries (which is more readable and expressive)  to do changes on a database.
- Since, it is Elixir code it is **Testable**. Imagine, you are writing some task to do changes to 1 million data. You need to be an SQL ninja and have a strong heart to directly run SQL queries directly on data. With `Task` you can write a Test case and be 100% sure that your query is going to work on the production.
- Also, if you have multiple environments you have to run the script one by one on all environments. With task, you will have just one file which you have to call with just one command.

### Let's write one!! 
- Continuing with the `user_type` column example I explained in the introduction. We will add that column to the User struct.
- You will add migration something like below
```
def change do
    alter table(:user) do
      add :user_type, :string
    end
end
```
- As per the new field you will add a field in the user form.
- Everything looks fine, now you are able to store value in this form but what about the old data? 
- As discussed we will be writing `task`.
- For writing a task add a file at `lib/mix/tasks/add_user_type.ex`.
- In the `add_user_type.ex` file we will add the following code.

```
 defmodule Mix.Tasks.AddUserType do
    use Mix.Task

    def run(_args) do
     # Write your task logic here
   end
end
```
- To create our task, we’ll need to start the module name with `Mix.Tasks`. So, we will name our module `Mix.Tasks.AddUserType`. 
-  We have added `use Mix.Task` macro, which adds task capability to this file.
-  To list the task within our application we can run `mix help` in the terminal of the project. You will get the list of the tasks but you'll not find the task that we created.
- It is because in order to list our task with a description we need to add the `@shortdoc` module attribute. We will add one.
```
 defmodule Mix.Tasks.AddUserType do
    use Mix.Task
    
    @shortdoc "Add user_type value to existing user data"

    def run(_args) do
     # Write your task logic here
   end
end
```
- Now on running `mix compile` and `mix help` again you can see the task we created (see the second line in the image below).

![Screenshot 2021-08-21 at 11.21.24 PM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629568381338/33GVCaNkr.png)
- So, far so good. Now to write the logic to change our data. It should be written in the `run` function.
- We are going to write our existing data changing logic inside the `run` function which takes one parameter `args`. We are going to ignore it because we don't need one.
- We will alias `User`, `Repo` and import `Ecto.Query` to write some SQL.
```
 defmodule Mix.Tasks.AddUserType do
    use Mix.Task
    
    @shortdoc "Add user_type value to existing user data"

    alias MyProject.{ Repo, User }
    import Ecto.Query

    def run(_args) do
        # Logic to change existing data.
        from(u in User, where: is_nil(u.user_type))
        |> Repo.update_all(set: [some_condition])
   end
end
```
- We will implement some logic to transform our old existing user data. Now, we will run the task.
- To run any task we the command `mix task_name` and in our case it will be `mix add_user_type`. 
- On running the task we will get this error `(RuntimeError) could not lookup MyProject.Repo because it was not started or it does not exist`.
- It is because our Repo isn’t started when we run our task, so we’ll need to do that manually.
- To run the task we need to pass `Repo` to `Mix.EctoSQL.ensure_started` function and also start the Task inside the `run` function.
```
    Mix.Task.run("app.start", [])
    Mix.EctoSQL.ensure_started(MyProject.Repo, [])
```
- Now, on running the task with the command `mix add_user_typ` it will run the task without any issue.
- Refer below complete running code 
```
 defmodule Mix.Tasks.AddUserType do
    use Mix.Task
    
    @shortdoc "Add user_type value to existing user data"

    alias MyProject.{ Repo, User }
    import Ecto.Query

    def run(_args) do
        Mix.Task.run("app.start", [])
        Mix.EctoSQL.ensure_started(MyProject.Repo, [])

        from(u in User, where: is_nil(u.user_type))
        |> Repo.update_all(set: [some_condition])
   end
end
```
### What about Test?
At the start of the blog while explaining the benefits of using `task` for writing the query. I emphasized writing the test to make sure our query is working fine. For writing the test you can add a test file in the folder `test/mix/task/add_user_type_test.ex` file and you can write the test for your task. We can cover about writing test for `mix task` in some other post.

 I hope you like this post. If you have any questions then please comment below. Thanks for reading 😊.

### References:
- [Cover Image](https://pmtips.net/Portals/0/EasyDNNNews/2137/700600p546EDNmainimg-3-types-of-tools-for-project-task-management1.jpg)









