Reducing some code duplication in Ash enums

I find myself defining a few enum modules in my application like this,
defmodule MyApp.MyDomain.SomeEnum do
use Ash.Type.Enum, values: [:foo, :bar, :blah]

def label_for(category) do
category
|> Atom.to_string()
|> String.capitalize()
end

@doc """
Returns a list suitable for passing to `MyAppWeb.CoreComponents.input/1` when
`type=select`.
"""
def options_for_select do
for category <- values() do
{label_for(category), category}
end
end
end
defmodule MyApp.MyDomain.SomeEnum do
use Ash.Type.Enum, values: [:foo, :bar, :blah]

def label_for(category) do
category
|> Atom.to_string()
|> String.capitalize()
end

@doc """
Returns a list suitable for passing to `MyAppWeb.CoreComponents.input/1` when
`type=select`.
"""
def options_for_select do
for category <- values() do
{label_for(category), category}
end
end
end
Would it be appropriate to move these functions that I have copied across modules into a module and then use the module to bring them into my enum modules?
Solution:
I added some functions to Enum awhile back for this. Pretty sure there are some examples in the docs. https://hexdocs.pm/ash/Ash.Type.Enum.html#module-value-labels-and-descriptions...
Jump to solution
11 Replies
aidalgol
aidalgolOP2mo ago
Something like this?
defmodule MyApp.MyDomain.SomeEnum do
use Ash.Type.Enum, values: [:foo, :bar, :blah]
use MyApp.EnumWebHelpers
defmodule MyApp.MyDomain.SomeEnum do
use Ash.Type.Enum, values: [:foo, :bar, :blah]
use MyApp.EnumWebHelpers
defmodule MyApp.EnumWebHelpers do
defmacro __using__(_opts) do
quote do
def label_for(category) do
category
|> Atom.to_string()
|> String.capitalize()
end

@doc """
Returns a list suitable for passing to `MyAppWeb.CoreComponents.input/1` when
`type=select`.
"""
def options_for_select do
for category <- values() do
{label_for(category), category}
end
end
end
end
end
defmodule MyApp.EnumWebHelpers do
defmacro __using__(_opts) do
quote do
def label_for(category) do
category
|> Atom.to_string()
|> String.capitalize()
end

@doc """
Returns a list suitable for passing to `MyAppWeb.CoreComponents.input/1` when
`type=select`.
"""
def options_for_select do
for category <- values() do
{label_for(category), category}
end
end
end
end
end
The reason I don't just move the functions into another module and call them from there is that there are a couple special-case enums for which the implementations of these functions need to be different. And would this be a candidate for a behaviour?
sevenseacat
sevenseacat2mo ago
I think the options_for_select functionality should be added to AshPhoenix - I'd generally err on the side of defining the labels for the enum values manually
aidalgol
aidalgolOP2mo ago
When I did that for some of my longer enums, it felt very repetitive, and more error-prone than ndefining the labels manually only for the exceptions.
sevenseacat
sevenseacat2mo ago
how does your code allow specifying labels for the exceptions?
aidalgol
aidalgolOP2mo ago
If options_for_select were added to AshPhoenix, what would it call to determine the labels? Completely different implementation of these functions. e.g., in my enum for NZ regions,
def label_for(region) do
case region do
:bop ->
"Bay of Plenty"

:hawkes_bay ->
"Hawke's Bay"

:manawatu_wanganui ->
"Manawatu-Wanganui"

other ->
other
|> Atom.to_string()
|> String.split("_")
|> Enum.map_join(" ", &String.capitalize(&1))
end
end
def label_for(region) do
case region do
:bop ->
"Bay of Plenty"

:hawkes_bay ->
"Hawke's Bay"

:manawatu_wanganui ->
"Manawatu-Wanganui"

other ->
other
|> Atom.to_string()
|> String.split("_")
|> Enum.map_join(" ", &String.capitalize(&1))
end
end
sevenseacat
sevenseacat2mo ago
ahhhh I see, the order of the use statements matter
aidalgol
aidalgolOP2mo ago
Is that generally considered an anti-pattern?
sevenseacat
sevenseacat2mo ago
I don't think so. it's not one I tend to use though
Solution
Chaz Watkins
Chaz Watkins2mo ago
I added some functions to Enum awhile back for this. Pretty sure there are some examples in the docs. https://hexdocs.pm/ash/Ash.Type.Enum.html#module-value-labels-and-descriptions
Chaz Watkins
Chaz Watkins2mo ago
The label/1 function does the same humanize string behavior as Phoenix.Naming.humanize These are overridable if you need to support translations. Agree we should add something to AshPhoenix for select options.
aidalgol
aidalgolOP2mo ago
Oh, cool! I missed those in the changelog.

Did you find this page helpful?