A trap with validate_length/3 of Ecto

2 minute read

validate_length/3

Quote to Ecto's document, this is the describe:

@spec validate_length(t(), atom(), Keyword.t()) :: t() Validates a change is a string or list of the given length.

Note that the length of a string is counted in graphemes by default. If using this validation to match a character limit of a database backend, it's likely that the limit ignores graphemes and limits the number of unicode characters. Then consider using the :count option to limit the number of codepoints (:codepoints), or limit the number of bytes (:bytes).

It's useful to validate any fields with the type of list.

But there is a hidden detail: validate_length/3 just validate the fields in `changes`.

Well, maybe you're thinking to yourself, "Isn't that nonsense? Don't be in a hurry, think about it, if you set the default value for the field in the schema, and the given value of the fields in params be the same as the dafault, what will be happened? Yes, validate_length/3 will not valid this field, you got no errors.

This action doesn't sound strange. To prevent the happen, I just need to remove the default for field. Actually, this problem is common, you can find the offical answer in this issue.

Slow down, just remove the default is the best way to solve? No! It broken the consistency of data, that's also why we set the default.

Solution

 1  def validate_length_include_existing(changeset, field, opts) do
 2    validate_value = get_field(changeset, field)
 3
 4    changeset
 5    |> force_change(field, validate_value)
 6    |> validate_length(field, opts)
 7    |> case do
 8      %{valid?: true} -> changeset
 9      %{errors: errors} -> %{changeset | valid?: false, errors: errors}
10    end
11  end

Why just get_field/2 ?

get_field/2 will get the value from changes first, if nil, from data, if else nil, from the default of field in schma. This is exactly what we need.

Why case do?

Actually the changeset which forced_change is different with the origin one. We just need the error and do not need the changes.