Passing EctoEnum to templates - protocol Enumerable not implemented for AdminRolesEnum of type Atom

Using html generator for a proj. I have an EctoEnum and I am trying to get this into a template to use in a drop down.

In it in a file:
defenum AdminRolesEnum, :admin_roles, [:owner, :developer, :admin, :editor, :contributor, :viewer]

I found this on how to make it work, but in reality it’s more complicated. It does work in the CLI though.

I’ve tried adding it to the template (preferred solution) but didn’t work. Ex:
<%= select f, :role, AdminRolesEnum |> Enum.map(&to_string(elem(&1, 0))), prompt: "Choose Role"%>

But I get this error: (Protocol.UndefinedError) protocol Enumerable not implemented for AdminRolesEnum of type Atom. I wish this worked as as this is how I’d want it.

Made new question with the rest: Passing variable through multiple templates

The module AdminRolesEnum can’t be directly piped into an Enum.map function, hence the error you’re getting. Elixir modules are actually Atoms behind the scenes if I recall correctly. If you really need/want to use the EctoEnum library, take a look at the functions below. The post you linked pipes the result of the enum map function into the Enum.map, not the module.

Reflection

The enum type StatusEnum will also have a reflection function for inspecting the enum map at runtime.

iex> StatusEnum.__enum_map__() 
[registered: 0, active: 1, inactive: 2, archived: 3] 
iex> StatusEnum.__valid_values__() 
[0, 1, 2, 3, :registered, :active, :inactive, :archived, "active", "archived", "inactive", "registered"]
1 Like

Thanks for the reply.

Having spent around 3 hours on this I’m thinking it’s not worth it. Is there any better way to do this? I mean, passing in a list that is already defined.

Or, do I have just had to hardcode the array inside the template? I hate this solution but it’s all I can get to work right now.

You could store it in the database on a separate table eg an AdminRole schema and admin_roles table with columns for role name and description. There are lots of guides on setting up user roles through associations out there.

If you want to hardcode it, maybe set it as a module attribute?

The only change needed here is to use the function that defenum has created for you, AdminRolesEnum.__enum_map__/0:

select f, :role, AdminRolesEnum.__enum_map__() |> Enum.map(&to_string(elem(&1, 0))), prompt: "Choose Role"

You might extract it to a function, if you had a lot of enums:

def enum_options_for(module) do
  Enum.map(module.__enum_map__(), fn {e, n} -> to_string(e) end)
end

to result in:

select f, :role, enum_options_for(AdminRolesEnum), prompt: "Choose Role"

If you have a LOT of these, you could even make an enum_select helper that encapsulates the select tag. Bonus points if it reflects on the type of the field given to find the right module to use.

1 Like

If I run AdminRolesEnum.__enum_map__() |> Enum.map(&to_string(elem(&1, 0))) from iex (or from within the html file)

I get: * 2nd argument: not a tuple

I’m also tried using it in a function like U suggest. I’m so new to Phoenix that I can’t even figure how to pass the function from the controller to the view. Not sure if this would make any difference anyway

`

I figured it out. Long winded beginner syntax, but fills in my select tag. Converts atoms to strings.
AdminRolesEnum.__enum_map__ |> Enum.map(fn x -> Atom.to_string(x) end)

[:owner, :developer, :admin, :editor, :contributor, :viewer]
=>
["owner", "developer", "admin", "editor", "contributor", "viewer"]

Glad you got it working!

If you still wanted to use the capture operator &, you can do the following.
AdminRolesEnum.__enum_map__ |> Enum.map(&Atom.to_string(&1))

And since it’s a 1 arity function, you can just specify the 1 arity like so.
AdminRolesEnum.__enum_map__ |> Enum.map(&Atom.to_string/1)

1 Like

Edit, realized I was commenting on EctoEnum and not Ecto.Enum. My bad