Absinth is a great GraphQL library for Elixir, but it brings a few challenges as it’s practically implemented using macros. One of these challenges is a DRY way of reusing enumerables in Absinth enums before v1.6.0.
The problem comes when you want to reuse a function that defines a list of values, like in the following case:
defmodule MyEnums do
@currencies [
:eur,
:pln,
:czk,
:huf
]
def currencies, do: @currencies
end
defmodule MyWeb.GQL.Types do
@moduledoc false
use Absinthe.Schema.Notation
enum(:currencies_enum,
values: MyEnums.currencies()
)
...
That won’t work in Absinth v1.5.x because enum
is a macro. Bummer.
Since I learned how to define my own macros, I ended up creating my own macros that would return the whole enum
call:
defmodule MyMacros do
# We create enum entries in Absinth as:
#
# enum :my_enum do
# value(:option_a, as: "option_a")
# value(:option_b, as: "option_b")
# end
#
# Which translates to:
#
# {:enum, [],
# [
# :my_enum,
# [
# do: {:__block__, [],
# [
# {:value, [], [:option_a, [as: "option_a"]]},
# {:value, [], [:option_b, [as: "option_b"]]}
# ]}
# ]
# ]}
defmacro currencies_enum() do
{:__block__, [],
{:enum, [],
[
:currencies_enum,
[
do:
{:__block__, [],
Enum.map(MyEnums.currencies(), fn value ->
{:value, [], [value]}
end)}
]
]}
end
end
Then you include it as:
defmodule MyWeb.GQL.Types do
@moduledoc false
use Absinthe.Schema.Notation
use MyMacros
currencies_enum()
...
While this works and provides excellent flexibility to do anything on the Absinth side, it’s complete overkill if the only thing you need is to provide an already existing list of values.
Luckily, there is a nice and tidy way how to do this!
We’ll define a currencies_macro
in the MyEnums
module with the Macro.expand/2
call:
defmodule MyEnums do
@currencies [
:eur,
:pln,
:czk,
:huf
]
def currencies, do: @currencies
defmacro currencies_macro, do: Macro.expand(@currencies, __CALLER__)
end
and then call this macro within the Absinth enum:
defmodule DigiWeb.Private.PacketsTypes do
@moduledoc false
use Absinthe.Schema.Notation
require MyEnums
enum(:currencies_enum,
values: MyEnums.currencies_macro()
)
...
end
The only thing we had to do is to use require
to require the module beforehand.
If you are on Absinth v1.6.0 and higher, Absinth now supports dynamic enum values so the very first example should work.