DEV Community

Sushant Bajracharya
Sushant Bajracharya

Posted on • Updated on

Using Enum in Ecto and Phoenix

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.

Top comments (0)