Subscribe to access all episodes. View plans →
Published August 19, 2021
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.
hminy572
3 years agoSupar amazing! Thanks for sharing!
Alekx
3 years agoAnytime! Thanks for watching :)
Diego Camacho
3 years agoGreat tip about Ecto.Enum.values!
Alekx
3 years agoThanks, Diego!