Ecto.Enum select underlying id

I seem to be stuck on some basic Ecto logic.

my Ecto version is 3.7.1

Given some schema:

schema :users do
  field :name, string
  field :zone, Ecto.Enum, values: ["Zone A": 1, "Zone B": 2, "Zone A and B": 3]  # stored as integer in db
  ..
end

FYI: I don’t want to use composite types of :array, neither :string type for the zone.

Then when I do query the data:

# simplified for brevity
from(
  u in Users,
  select: [:id, :name, :zone],
  where: [name: ^some_name]
)
|> Repo.all()

Each user object’s zone is shown as atom:

%MyApp.Users.User{
  __meta__: ...,
  id: 1,
  name: "User A",
  zone: :"Zone A"  # I need this as integer
}

How do I make Ecto return the corresponding ids for zone field?

The reason I need that is later in the code I’m trying to create two list of users (with one db query), like so:

users  = Users.get_all_active_users()
users_zone_a = Enum.reject(users, fn u -> u.zone == 2 end)
users_zone_b = Enum.reject(users, fn u -> u.zone == 1 end)

Any help greatly appreciated :slight_smile:

That’s the idea of the enum in this case, to separate the presented value from the persisted one. What I would advise is to create a helper module which can encode and decode integers to and from :"Zone A", :"Zone B" and so on. You should do the encoding/decoding at the “points” where your code interacts with the external word e.g. HTTP endpoints, database. This would allow you to work only with meaningful values and your comparisons will work.

I realised (during breakfast…) that I could filter by the atom instead
e.g.

Enum.reject(users, fn u ->
  u.zone == :"Zone A"
end)

Not ideal but it works. Probably would be wise to have some encode/decode helper for those so it’s not spread all over the codebase.

The whole idea of Ecto.Enum is to not spread the persisted value (the integers in your case) all over the codebase. You’re expected to use the atoms wherever you interact with the field, not the integers. Otherwise you could just use an :integer field to begin with.

But also there’s Ecto.Enum.mappings if you want to map between the representations.

6 Likes