Current section
Files
Jump to
Current section
Files
lib/changeset_validator.ex
defmodule Formex.Ecto.ChangesetValidator do
@behaviour Formex.Validator
alias Formex.Form
alias Formex.Field
alias Ecto.Changeset
@moduledoc """
Changeset validator adapter for Formex.
It was created to make use of validation functions included in `Ecto.Changeset`. This module
creates a fake changeset to perform validation rules.
You don't need to use this validator - any
[validator](https://hexdocs.pm/formex/Formex.Validator.html) works with Ecto schemas.
You can also add errors in the `c:Formex.Ecto.modify_changeset/2` callback, which modifies real
changeset.
# Limitations
* can be used only with Ecto schemas.
* `length` validation for collections doesn't work.
Maybe there is a way to fix it. If you need this now - use Vex validator instead.
# Installation
See `Formex.Validator` docs.
# Usage
```
defmodule App.UserType do
use Formex.Type
use Formex.Ecto.Type
use Formex.Ecto.ChangesetValidator # <- add this
```
```
def build_form(form) do
form
|> add(:username, :text_input, validation: [
:required
])
|> add(:email, :text_input, validation: [
required: [message: "give me your email!"],
format: [arg: ~r/@/]
])
|> add(:age, :text_input, validation: [
:required,
inclusion: [arg: 13..100, message: "you must be 13."]
])
end
```
Keys from `validation` list are converted to `validate_` functions from
`Ecto.Changeset`. For example `required` -> `Ecto.Changeset.validate_required/3`.
Value is list of options. If function requires additional argument
(e.g. `Ecto.Changeset.validate_format/4` needs format as third argument)
it must be passed as `:arg` option.
"""
defmacro __using__([]) do
quote do
import Ecto.Changeset
end
end
@spec validate(Form.t) :: Form.t
def validate(form) do
# the `create_changeset_for_validation` creates changeset without collections
# `length` doesn't validate empty collections so we don't need them
changeset = Formex.Ecto.Changeset.create_changeset_for_validation(form)
errors_fields = form
|> Form.get_fields_validatable
|> Enum.flat_map(fn item ->
validate_field(changeset, item)
end)
errors = errors_fields
|> Enum.reduce([], fn ({key, val}, acc) ->
Keyword.update(acc, key, [val], &([val|&1]))
end)
form
|> Map.put(:errors, errors)
end
@spec validate_field(changeset :: Changeset.t, field :: Field.t) :: List.t
defp validate_field(changeset, field) do
field.validation
|> Enum.reduce(changeset, fn (validation, changeset) ->
{name, opts} = case validation do
{name, opts} when is_list(opts)
-> {name, opts}
name
-> {name, []}
end
{arg, opts} = Keyword.pop(opts, :arg)
args = if arg do
[changeset, field.name, arg, opts]
else
[changeset, field.name, opts]
end
name = "validate_"<>to_string(name) |> String.to_atom
apply(Changeset, name, args)
end)
|> Map.get(:errors)
end
end