I’m wring a custom Ecto Type that behaves like an enum type. I have a keyword list of atoms => integers.
I realised I can use Elixir Modules (which are really just atoms) for my keys, instead regular atoms. I’m not sure if this is poor form though.
To me, using modules 1) looks more important and obvious that acceptable values are limited in scope and 2) provides some chance for correct code suggestion.
Since they’re “just atoms”, I don’t believe doing this incurs any more performance penalty than I otherwise would? At the same time, passing regular atoms seems a bit more natural to elixir?
I’m not too worried about the wrong atom being inserted into the DB since I check at cast/dump using either methods, it’s more of a stylistic question.
Whats the best practice here? What would you do?
module-as-key
defmodule App.Background.BackgroundJob.Pool do
# you could probably macro this if you hated the look of the syntax
defmodule Local do end
defmodule Remote do end
@behaviour Ecto.Type
@pools [{Local, 0}, {Remote, 1}]
# cast, load, etc...
end
job = %{
pool: BackgroundJob.Pool.Local,
# ...
}
atom-as-key
defmodule App.Background.BackgroundJob.Pool do
@behaviour Ecto.Type
@pools [{:local, 0}, {:remote, 1}]
# possibly have these functions
def local, do: :local
def remote, do: :remote
# cast, load, etc...
end
job = %{
pool: BackgroundJob.Pool.local,
# ...
}
# or
job = %{
pool: :local,
# ...
}
There is no silver bullet. I often use elixir modules as keys, but only when it makes sense for them to be a module (additional functionality, functions, whatever.)
Here you use modules for the sake of saving several keystrokes only. It looks misleading and obtrusive to me. To avoid code repetition and by DRY one can simply turn your module into a struct:
defmodule App.Background.BackgroundJob.Pool do
@enum [local: 0, remote: 1]
defstruct @enum
@behaviour Ecto.Type
@pools @enum
# cast, load, etc...
end
job = %{
pool: BackgroundJob.Pool.local,
# ...
}
or any other way to provide more clarity, like macros:
defmodule Enum do
defmacro __using__(enum) do
enum
|> Enum.with_index()
|> Enum.map(fn {name, idx} ->
quote location: :keep do
def unquote(name)(), do: unquote(idx)
end
end)
end
end
use Enum, [:local, :remote]
job = %{
pool: BackgroundJob.Pool.local,
# ...
}