Using Enum in Ecto and Phoenix
Tags: phoenix, elixir, ecto
Published: 2019-09-15 13:01:46

NOTE:

The internet is filled with programming articles written by inexperienced programmers. Same is with this article so read it with a grain of salt.

=====================================================================

If you are reading this, then chances are pretty wild that you know what enum is and why you want it. So, I wont bother wasting your time explaining them. Let's get straight to the point.


```
defmodule Stipe.Repo.Migrations.AlterDailyUpdate do
  use Ecto.Migration

  def up do
    execute("CREATE TYPE daily_update_status AS ENUM('In Progress', 'In Testing', 'Done');")
    execute("ALTER TABLE daily_updates ALTER COLUMN status DROP DEFAULT;")
    execute("
      ALTER TABLE daily_updates 
              ALTER COLUMN status TYPE daily_update_status 
                USING 
                  CASE status 
                    WHEN NULL then 'In Progress'
                    WHEN 0 then 'In Progress'
                  end :: daily_update_status;
    ")
    execute("
      ALTER TABLE daily_updates
        ALTER COLUMN status SET DEFAULT 'In Progress';  
      ")
  end

  def down do
    execute("ALTER TABLE daily_updates ALTER COLUMN status DROP DEFAULT;")
    execute("
        ALTER TABLE daily_updates
          ALTER COLUMN status TYPE INT 
            USING 
              CASE status
                WHEN NULL then 0
                WHEN 'In Progress' then 0
                WHEN 'In Testing' then 1
                WHEN 'Done' then 2
              end :: integer;
      ")
    execute("
        ALTER TABLE daily_updates
          ALTER COLUMN status SET DEFAULT 0;
      ")
    execute("DROP TYPE IF EXISTS daily_update_status;")
  end
end
```

This piece of code is a migration which alters the structure of the table `DailyUpdates`. Previously, I had set `status` column with integer but now I wanted to use postgres enum.

I like to define `up` and `down` function whenever I am using `execute` because when we use `execute`, migrations are not reversible.

The first line in `up` function is creating an enum data type. Then it drops any default value that was set from the previous migration. The enum data type is then assigned to the `status` column. If the status column has data 0 or NULL then it will be mapped. Anything else than that and the migration will fail.


The `down` function is reverting the migration.


Now, that we are done with migrations, let's go to our schema.

```
defmodule Stipe.Standup.DailyUpdate do
  use Ecto.Schema
  import Ecto.Changeset
  alias Stipe.Accounts.User

  schema "daily_updates" do
    field :remarks, :string
    field :started_on, :date
    field :status, :string
    field :task_number, :string
    field :time_spent, :decimal
    belongs_to :user, User
    timestamps()
  end

  def statuses do
    ["In Progress": "In Progress", "In Testing": "In Testing", Done: "Done"]
  end

  @doc false
  def changeset(daily_update, attrs) do
    daily_update
    |> cast(attrs, [:task_number, :status, :time_spent, :started_on, :remarks])
    |> cast_assoc(:user)
    |> assoc_constraint(:user)
    |> validate_required([:task_number, :time_spent, :started_on, :remarks])
  end
end

```

Normally we define our `status` field as type string. I have also defined `statuses` function that returns a list of our supported statuses. We can use this function to populate the form.

To use the statuses function in template, we will alias the Stipe.Standup.DailyUpdate in the view.

```
defmodule StipeWeb.DailyUpdateView do
  use StipeWeb, :view
  alias Stipe.Standup.DailyUpdate
  import Stipe.Utils.Date
end

```

In our template, we can call `DailyUpdate.statuses`

```
<%= form_for @changeset, @action, fn f -> %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below.</p>
    </div>
  <% end %>

  <%= label f, :task_number %>
  <%= text_input f, :task_number %>
  <%= error_tag f, :task_number %>

  <%= label f, :status %>
  <%= select f, :status, DailyUpdate.statuses %>
  <%= error_tag f, :status %>

  <%= label f, :time_spent %>
  <%= number_input f, :time_spent, step: "any" %>
  <%= error_tag f, :time_spent %>

  <%= label f, :started_on %>
  <%= date_input f, :started_on %>
  <%= error_tag f, :started_on %>

  <%= label f, :remarks %>
  <%= text_input f, :remarks %>
  <%= error_tag f, :remarks %>

  <div>
    <%= submit "Save" %>
  </div>
<% end %>
```

It will list the supported statuses and also select the chosen status on edit.