Skip to content

Instantly share code, notes, and snippets.

@myobie
Created May 26, 2021
Embed
What would you like to do?
I'm not saying this is a good idea, but I'm not not saying it
defmodule POC.Params do
import Ecto.Changeset
@spec permit(map, map) :: map
def permit(input, types) do
{types, nested} = Enum.reduce(types, {%{}, %{}}, &process_types/2)
{%{}, types}
|> cast(input, Map.keys(types))
|> permit_nested(nested)
|> apply_changes()
end
@spec process_types({atom, atom | map}, {map, map}) :: {map, map}
defp process_types({name, {:array, %{} = nested_types}}, {types, nested}) do
types = Map.put(types, name, {:array, :map})
nested = Map.put(nested, name, {:array, nested_types})
{types, nested}
end
defp process_types({name, %{} = nested_types}, {types, nested}) do
# NOTE: when types are nested we use :map to tell Ecto.Changeset to cast it
# as a map, then we later will permit_nested to cast down into that map
types = Map.put(types, name, :map)
nested = Map.put(nested, name, nested_types)
{types, nested}
end
defp process_types({name, type}, {types, nested}) do
types = Map.put(types, name, type)
{types, nested}
end
@spec permit_nested(Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
defp permit_nested(changeset, nested) do
Enum.reduce(nested, changeset, &permit_field/2)
end
@spec permit_field({atom, map}, Ecto.Changeset.t()) :: Ecto.Changeset.t()
defp permit_field({field, {:array, types}}, changeset) do
changeset
|> Ecto.Changeset.update_change(field, fn arr ->
Enum.map(arr, &permit(&1, types))
end)
end
defp permit_field({field, types}, changeset) do
changeset
|> Ecto.Changeset.update_change(field, fn attrs ->
permit(attrs, types)
end)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment