improvement: before_action? on validate, validate inline

This commit is contained in:
Zach Daniel 2021-04-01 11:53:52 -04:00
parent 274fc9ea88
commit 92344029d3
7 changed files with 90 additions and 30 deletions

View file

@ -12,6 +12,7 @@ locals_without_parens = [
attribute: 2,
attribute: 3,
base_filter: 1,
before_action?: 1,
belongs_to: 2,
belongs_to: 3,
calculate: 3,

View file

@ -636,11 +636,7 @@ defmodule Ash.Changeset do
module.change(changeset, opts, %{actor: actor})
%{validation: _} = validation, changeset ->
if validation.expensive? and not changeset.valid? do
changeset
else
do_validation(changeset, validation)
end
validate(changeset, validation)
end)
end
@ -693,19 +689,29 @@ defmodule Ash.Changeset do
defp default(:update, %{update_default: value}), do: value
defp add_validations(changeset) do
Ash.Changeset.before_action(changeset, fn changeset ->
changeset.resource
# We use the `changeset.action_type` to support soft deletes
# Because a delete is an `update` with an action type of `update`
|> Ash.Resource.Info.validations(changeset.action_type)
|> Enum.reduce(changeset, fn validation, changeset ->
changeset.resource
# We use the `changeset.action_type` to support soft deletes
# Because a delete is an `update` with an action type of `update`
|> Ash.Resource.Info.validations(changeset.action_type)
|> Enum.reduce(changeset, &validate(&2, &1))
end
defp validate(changeset, validation) do
if validation.before_action? do
before_action(changeset, fn changeset ->
if validation.expensive? and not changeset.valid? do
changeset
else
do_validation(changeset, validation)
end
end)
end)
else
if validation.expensive? and not changeset.valid? do
changeset
else
do_validation(changeset, validation)
end
end
end
defp do_validation(changeset, validation) do

View file

@ -39,7 +39,16 @@ defmodule Ash.Resource.Validation do
end
```
"""
defstruct [:validation, :module, :opts, :expensive?, :description, :message, on: []]
defstruct [
:validation,
:module,
:opts,
:expensive?,
:description,
:message,
:before_action?,
on: []
]
defmacro __using__(_) do
quote do
@ -92,6 +101,12 @@ defmodule Ash.Resource.Validation do
description: [
type: :string,
doc: "An optional description for the validation"
],
before_action?: [
type: :boolean,
default: false,
doc:
"If set to `true`, the validation is not run when building changesets using `Ash.Changeset.for_*`. The validation will only ever be run once the action itself is called."
]
]

View file

@ -35,7 +35,7 @@ defmodule Ash.Resource.Validation.Confirm do
Changeset.get_argument(changeset, opts[:field]) ||
Changeset.get_attribute(changeset, opts[:field])
if confirmation_value == value do
if Comp.equal?(confirmation_value, value) do
:ok
else
{:error,

View file

@ -36,20 +36,38 @@ defmodule Ash.Resource.Validation.Match do
@impl true
def validate(changeset, opts) do
case Ash.Changeset.fetch_change(changeset, opts[:attribute]) do
{:ok, changing_to} when is_binary(changing_to) ->
if String.match?(changing_to, opts[:match]) do
:ok
else
{:error,
InvalidAttribute.exception(
field: opts[:attribute],
message: opts[:message],
vars: [match: opts[:match]]
)}
{:ok, changing_to} ->
case string_value(changing_to, opts) do
{:ok, changing_to} ->
if String.match?(changing_to, opts[:match]) do
:ok
else
{:error,
InvalidAttribute.exception(
field: opts[:attribute],
message: opts[:message],
vars: [match: opts[:match]]
)}
end
{:error, error} ->
{:error, error}
end
_ ->
:ok
end
end
defp string_value(value, opts) do
{:ok, to_string(value)}
rescue
_ ->
{:error,
InvalidAttribute.exception(
field: opts[:attribute],
message: opts[:message],
vars: [match: opts[:match]]
)}
end
end

View file

@ -37,7 +37,7 @@ defmodule Ash.Resource.Validation.OneOf do
:ok
{:ok, changing_to} ->
if changing_to in opts[:values] do
if Enum.any?(opts[:values], &Comp.equal?(&1, changing_to)) do
:ok
else
{:error,

View file

@ -36,12 +36,32 @@ defmodule Ash.Resource.Validation.StringLength do
@impl true
def validate(changeset, opts) do
Ash.Changeset.get_attribute(changeset, opts[:attribute])
|> do_validate(Enum.into(opts, %{}))
end
case Ash.Changeset.get_attribute(changeset, opts[:attribute]) do
nil ->
:ok
defp do_validate(nil, _) do
:ok
value ->
value =
try do
{:ok, to_string(value)}
rescue
_ ->
{:error,
InvalidAttribute.exception(
field: opts[:attribute],
message: "%{field} could not be parsed",
vars: [field: opts[:attribute]]
)}
end
case value do
{:ok, value} ->
do_validate(value, Enum.into(opts, %{}))
{:error, error} ->
{:error, error}
end
end
end
defp do_validate(value, %{exact: exact} = opts) do