Subscribe to access all episodes. View plans →

#135: Ecto.Enum

Published August 19, 2021

Ecto.Enum

Follow along with the episode starter on GitHub


Here we have an Elixir app that uses Phoenix LiveView and this page here allows us to add users to our application. As you can see there’s a label for “Permissions”, but there’s no way to select permissions for the user.

Let’s create a list of permissions we can select from and let’s store permission value as an Atom that we can compare with. Now we are going to hit a problem trying to implement this because Atom’s can’t be persisted to the database. Luckily Ecto provides the Ecto.Enum type as a workaround. It will convert the atom to a string or integer when writing to the database, and then back to an atom when reading from it. Let’s use this for our permissions field.

To get started we’ll need to add the permissions field to our users schema. Let’s go to the command line and generate a migration to add the column.

$ mix ecto.gen.migration users_add_permissions_column
* creating priv/repo/migrations/{timestamp}_add_permissions_to_users.exs

Then let’s open the generated migration and we can use the alter function to add a column to our users table. Then we’ll add a column named permissions and for the type, we’ll need to use either a string or an integer. For our example here let’s use a string.

priv/repo/migrations/{timestamp}_add_permissions_to_users.exs

defmodule Teacher.Repo.Migrations.AddPermissionsToUsers do
  use Ecto.Migration

  def change do
    alter table(:users) do
      add :permissions, :string
    end
  end
end

Then we can go back to the command line and run our migration.

$ mix ecto.migrate
...

Great now let’s open the user.ex schema module and add the permissions field to the schema specifying the Ecto.Enum type and then we’ll need to specify a list of values. We’ll only be able to cast the value of the permissions to an atom if it’s included in the list here. If we try to use any string other than “Read”, “Write”, or “Admin” we should get an error. Alright, now let’s go ahead and add permissions to our changeset changeset and we’ll need every user to have permissions so we’ll make it required.

lib/teacher/accounts/user.ex

defmodule Teacher.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :email, :string
    field :name, :string
    field :permissions, Ecto.Enum, values: [:Read, :Write, :Admin]

    timestamps()
  end

  @doc false
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name, :email, :permissions])
    |> validate_required([:name, :email, :permissions])
    |> unique_constraint(:email)
  end

end

Now that we can store the user’s permissions we’ll need to update our user form. We’ll open the form_component.html.leex and add the permissions dropdown including some permissions we can choose from and let’s include one value, :Superuser that we didn’t specify in our permissions field. If we try to choose it we should get an error. We’ll also want to include the error_tag for our permissions so we can see the error in the UI.

Template path: lib/teacher_web/live/user_live/form_component.html.leex

...
<%= label f, :permissions %>
<%= select f, :permissions, [:Read, :Write, :Admin, :Superuser] %>
<%= error_tag f, :permissions %>
...

Now let’s go to the command line and start the server.

$ mix phx.server
...

Then let’s go back to the browser … and add a user to our application. And let’s give this user “Admin” permissions. It looks like they were added and if we open our edit form - perfect they were added with the correct permission. But if we try to update their permissions to the “Superuser” that we didn’t include we see an error because it’s not included in the values for our permissions.

Now if our application grows and we have a lot of references for the allowed permissions it can become a lot of work to find each reference and update it. So instead of hardcoding our permission values, let’s try something different.

Let’s go to the command line and start an IEx session with our application. We can call Ecto.Enum.values with our schema module, Teacher.Accounts.User and then the permissions field. This returns the values specified in the schema. Let’s use this in our template so we’re always displaying a valid list of options for dropdown.

$ iex -S mix
> Ecto.Enum.values(Teacher.Accounts.User, :permissions)
[:Read, :Write, :Admin]

We’ll go back to our form_component.html.leex template and update it to get the values using the Ecto.Enum.values function.

Template path: lib/teacher_web/live/user_live/form_component.html.leex

...
<%= label f, :permissions %>
<%= select f, :permissions, Ecto.Enum.values(Teacher.Accounts.User, :permissions) %>
<%= error_tag f, :permissions %>
...

Then let’s start our server again.

$ mix phx.server
...

And if we go to edit our user great we only see the allowed values for permissions. Now what if we wanted to store multiple permissions for a user, how could we do that with Ecto.Enum?

First, let’s go to the command line and roll back our migration.

$ mix ecto.rollback
... 

Then let’s open our {timestamp}_add_permissions_to_users.exs and instead of the :string type we’ll change it to {:array, :string}. This will allow us to store a list of permissions for a user - each one cast to an atom.

priv/repo/migrations/{timestamp}_add_permissions_to_users.exs

...

add :permissions, {:array, :string}, default: []

...

Then let’s go to the command line and migrate our database.

$ mix ecto.migrate
...

With that, we can go back to our user.ex module and update the type of the :permissions field.

lib/teacher/accounts/user.ex

...

field :permissions, {:array, Ecto.Enum}, values: [:Read, :Write, :Admin]

...

Now that we can store multiple permissions for a user, let’s update our form to allow it. We’ll go back to our form_component.html.leex and update it to use the multiple_select helper function.

Template path: lib/teacher_web/live/user_live/form_component.html.leex

...
<%= multiple_select f, :permissions, Ecto.Enum.values(Teacher.Accounts.User, :permissions) %>
...

With those changes, we can go back to the command line and start our server one last time.

$ mix phx.server
...

Then let’s update our user and choose a few different permissions. And great - the permissions were saved. Our application is updated to allow us to store a list of user permissions and have them safely be cast to atoms.

© 2024 HEXMONSTER LLC