I wanted to reply on Twitter but I saw there is already a solution in here. I’d like to share my anyway ![]()
Since we need to traverse the schema many times, I normalize it first and then use list comprehensions like this:
def parse(params, schema) do
# First I want to have entire schema in one format [{key, {type, opts}}]
normalized_schema = for {key, type_spec} <- schema, do: {key, apply_default_opts(type_spec)}
keys = for {key, _} <- normalized_schema, do: key
types = for {key, {type, _opts}} <- normalized_schema, into: %{}, do: {key, type}
required_fields = for {key, {_type, opts}} <- normalized_schema, Keyword.get(opts, :required), do: key
defaults = for {key, {_type, opts}} <- normalized_schema, default = Keyword.get(opts, :default), into: %{}, do: {key, default}
{defaults, types}
|> cast(params, keys)
|> validate_required(required_fields)
|> apply_action(:normalize)
end
@default_opts [required: false]
defp apply_default_opts(type) when is_atom(type), do: {type, @default_opts}
defp apply_default_opts({type, opts}), do: {type, Keyword.merge(@default_opts, opts)}
The line computing defaults might be tricky to understand because it is long and introduces a variable inside the comprehension filter.
Also, I’d vote for naming that function “parse” instead of normalize. Parsing is an action of taking a bunch of data and trying to transform it into a structure that is understandable. In the spirit of Parse, don’t validate
It would be possible to push the schema specification even more to introduce validations like:
email: {:string, format: ~r/@/}
and then call Changeset.validate_format(changeset, key, format) for each. But that would require another option for each Ecto.Changeset validatior. It might be an overkill but the schema format is very readable so it is tempting ![]()






















