mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
improvement: add some atomic implementations
This commit is contained in:
parent
746fc5df53
commit
6061a2a16a
10 changed files with 172 additions and 29 deletions
|
@ -567,30 +567,42 @@ defmodule Ash.Changeset do
|
|||
end
|
||||
|
||||
%{validation: {module, validation_opts}, where: where}, changeset ->
|
||||
with {:atomic, fields, condition_expr, error_expr} <-
|
||||
module.atomic(changeset, validation_opts),
|
||||
{:changing?, true} <-
|
||||
{:changing?, Enum.any?(fields, &changing_attribute?(changeset, &1))},
|
||||
{:atomic, condition} <- atomic_condition(where, changeset) do
|
||||
case condition do
|
||||
true ->
|
||||
{:cont, validate_atomically(changeset, condition_expr, error_expr)}
|
||||
case List.wrap(module.atomic(changeset, validation_opts)) do
|
||||
[{:atomic, _, _, _} | _] = atomics ->
|
||||
Enum.reduce_while(atomics, changeset, fn
|
||||
{:atomic, fields, condition_expr, error_expr}, changeset ->
|
||||
with {:changing?, true} <-
|
||||
{:changing?,
|
||||
fields == :* || Enum.any?(fields, &changing_attribute?(changeset, &1))},
|
||||
{:atomic, condition} <- atomic_condition(where, changeset) do
|
||||
case condition do
|
||||
true ->
|
||||
{:cont, validate_atomically(changeset, condition_expr, error_expr)}
|
||||
|
||||
false ->
|
||||
{:cont, changeset}
|
||||
false ->
|
||||
{:cont, changeset}
|
||||
|
||||
condition ->
|
||||
condition_expr =
|
||||
Ash.Expr.expr(^condition and condition_expr)
|
||||
condition ->
|
||||
condition_expr =
|
||||
Ash.Expr.expr(^condition and condition_expr)
|
||||
|
||||
{:cont, validate_atomically(changeset, condition_expr, error_expr)}
|
||||
end
|
||||
else
|
||||
:not_atomic ->
|
||||
{:halt, :not_atomic}
|
||||
{:cont, validate_atomically(changeset, condition_expr, error_expr)}
|
||||
end
|
||||
else
|
||||
{:changing?, false} ->
|
||||
{:cont, changeset}
|
||||
|
||||
{:changing?, false} ->
|
||||
{:cont, changeset}
|
||||
:not_atomic ->
|
||||
{:halt, :not_atomic}
|
||||
end
|
||||
end)
|
||||
|> case do
|
||||
:not_atomic -> {:halt, :not_atomic}
|
||||
changeset -> {:cont, changeset}
|
||||
end
|
||||
|
||||
[value] ->
|
||||
{:halt, value}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
@ -630,13 +642,24 @@ defmodule Ash.Changeset do
|
|||
Gets a reference to a field, or the current atomic update expression of that field.
|
||||
"""
|
||||
def atomic_ref(changeset, field) do
|
||||
if base_value = changeset.atomics[field] do
|
||||
%{type: type, constraints: constraints} =
|
||||
Ash.Resource.Info.attribute(changeset.resource, field)
|
||||
case Keyword.fetch(changeset.atomics, field) do
|
||||
{:ok, atomic} ->
|
||||
%{type: type, constraints: constraints} =
|
||||
Ash.Resource.Info.attribute(changeset.resource, field)
|
||||
|
||||
Ash.Expr.expr(type(^base_value, ^type, ^constraints))
|
||||
else
|
||||
Ash.Expr.expr(ref(^field))
|
||||
Ash.Expr.expr(type(^atomic, ^type, ^constraints))
|
||||
|
||||
:error ->
|
||||
case Map.fetch(changeset.attributes, field) do
|
||||
{:ok, new_value} ->
|
||||
%{type: type, constraints: constraints} =
|
||||
Ash.Resource.Info.attribute(changeset.resource, field)
|
||||
|
||||
Ash.Expr.expr(type(^new_value, ^type, ^constraints))
|
||||
|
||||
:error ->
|
||||
Ash.Expr.expr(ref(^field))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -48,8 +48,12 @@ defmodule Ash.Resource.Validation do
|
|||
@callback atomic?() :: boolean
|
||||
@callback atomic(changeset :: Ash.Changeset.t(), opts :: Keyword.t()) ::
|
||||
:ok
|
||||
| {:atomic, involved_fields :: list(atom), condition_expr :: Ash.Expr.t(),
|
||||
| {:atomic, involved_fields :: list(atom) | :*, condition_expr :: Ash.Expr.t(),
|
||||
error_expr :: Ash.Expr.t()}
|
||||
| [
|
||||
{:atomic, involved_fields :: list(atom) | :*, condition_expr :: Ash.Expr.t(),
|
||||
error_expr :: Ash.Expr.t()}
|
||||
]
|
||||
| :not_atomic
|
||||
| {:error, term()}
|
||||
|
||||
|
|
|
@ -15,6 +15,11 @@ defmodule Ash.Resource.Validation.ActionIs do
|
|||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def atomic(changeset, opts) do
|
||||
validate(changeset, opts)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def describe(opts) do
|
||||
[message: "must be %{action}", vars: %{action: opts[:action]}]
|
||||
|
|
|
@ -43,6 +43,11 @@ defmodule Ash.Resource.Validation.ArgumentDoesNotEqual do
|
|||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def atomic(changeset, opts) do
|
||||
validate(changeset, opts)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def describe(opts) do
|
||||
[
|
||||
|
|
|
@ -42,6 +42,11 @@ defmodule Ash.Resource.Validation.ArgumentEquals do
|
|||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def atomic(changeset, opts) do
|
||||
validate(changeset, opts)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def describe(opts) do
|
||||
[
|
||||
|
|
|
@ -42,6 +42,11 @@ defmodule Ash.Resource.Validation.ArgumentIn do
|
|||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def atomic(changeset, opts) do
|
||||
validate(changeset, opts)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def describe(opts) do
|
||||
[
|
||||
|
|
|
@ -16,6 +16,7 @@ defmodule Ash.Resource.Validation.AttributeIn do
|
|||
|
||||
use Ash.Resource.Validation
|
||||
alias Ash.Error.Changes.InvalidAttribute
|
||||
require Ash.Expr
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
|
@ -42,11 +43,26 @@ defmodule Ash.Resource.Validation.AttributeIn do
|
|||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def atomic(changeset, opts) do
|
||||
field_value = Ash.Changeset.atomic_ref(changeset, opts[:attribute])
|
||||
|
||||
{:atomic, [opts[:attribute]], Ash.Expr.expr(^field_value in ^opts[:list]),
|
||||
Ash.Expr.expr(
|
||||
error(^InvalidAttribute, %{
|
||||
field: ^opts[:attribute],
|
||||
value: ^field_value,
|
||||
message: "must be in %{list}",
|
||||
vars: %{field: ^opts[:attribute], list: ^opts[:list]}
|
||||
})
|
||||
)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def describe(opts) do
|
||||
[
|
||||
message: "must equal %{value}",
|
||||
vars: [field: opts[:attribute], value: opts[:value]]
|
||||
message: "must be in %{list}",
|
||||
vars: [field: opts[:attribute], list: opts[:list]]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule Ash.Resource.Validation.Changing do
|
|||
use Ash.Resource.Validation
|
||||
|
||||
alias Ash.Error.Changes.InvalidAttribute
|
||||
require Ash.Expr
|
||||
|
||||
@opt_schema [
|
||||
field: [
|
||||
|
@ -51,6 +52,22 @@ defmodule Ash.Resource.Validation.Changing do
|
|||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def atomic(changeset, opts) do
|
||||
new_value = Ash.Changeset.atomic_ref(changeset, opts[:field])
|
||||
old_value = Ash.Expr.expr(ref(^opts[:field]))
|
||||
|
||||
{:atomic, [opts[:field]], Ash.Expr.expr(^new_value != ^old_value),
|
||||
Ash.Expr.expr(
|
||||
error(^InvalidAttribute, %{
|
||||
field: ^opts[:field],
|
||||
value: ^new_value,
|
||||
message: "must be changing",
|
||||
vars: %{field: ^opts[:field]}
|
||||
})
|
||||
)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def describe(_opts) do
|
||||
[
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule Ash.Resource.Validation.Compare do
|
|||
use Ash.Resource.Validation
|
||||
|
||||
alias Ash.Error.Changes.InvalidAttribute
|
||||
require Ash.Expr
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
|
@ -67,6 +68,66 @@ defmodule Ash.Resource.Validation.Compare do
|
|||
end
|
||||
end
|
||||
|
||||
def atomic(changeset, opts) do
|
||||
case Ash.Changeset.fetch_argument(changeset, opts[:attribute]) do
|
||||
:error ->
|
||||
ref = Ash.Changeset.atomic_ref(changeset, opts[:attribute])
|
||||
|
||||
opts
|
||||
|> Keyword.take([
|
||||
:greater_than,
|
||||
:less_than,
|
||||
:greater_than_or_equal_to,
|
||||
:less_than_or_equal_to
|
||||
])
|
||||
|> Enum.map(fn
|
||||
{:greater_than, value} ->
|
||||
{:atomic, [opts[:attribute]], Ash.Expr.expr(^ref <= ^value),
|
||||
Ash.Expr.expr(
|
||||
error(^InvalidAttribute, %{
|
||||
field: ^opts[:attribute],
|
||||
value: ^ref,
|
||||
message: "must be greater than %{greater_than}",
|
||||
vars: %{field: ^opts[:attribute], greater_than: ^value}
|
||||
})
|
||||
)}
|
||||
|
||||
{:less_than, value} ->
|
||||
{:atomic, [opts[:attribute]], Ash.Expr.expr(^ref >= ^value),
|
||||
Ash.Expr.expr(
|
||||
error(^InvalidAttribute, %{
|
||||
field: ^opts[:attribute],
|
||||
value: ^ref,
|
||||
message: "must be less than %{less_than}",
|
||||
vars: %{field: ^opts[:attribute], less_than: ^value}
|
||||
})
|
||||
)}
|
||||
|
||||
{:greater_than_or_equal_to, value} ->
|
||||
{:atomic, [opts[:attribute]], Ash.Expr.expr(^ref < ^value),
|
||||
Ash.Expr.expr(
|
||||
error(^InvalidAttribute, %{
|
||||
field: ^opts[:attribute],
|
||||
value: ^ref,
|
||||
message: "must be greater than or equal to %{greater_than_or_equal_to}",
|
||||
vars: %{field: ^opts[:attribute], greater_than_or_equal_to: ^value}
|
||||
})
|
||||
)}
|
||||
|
||||
{:less_than_or_equal_to, value} ->
|
||||
{:atomic, [opts[:attribute]], Ash.Expr.expr(^ref > ^value),
|
||||
Ash.Expr.expr(
|
||||
error(^InvalidAttribute, %{
|
||||
field: ^opts[:attribute],
|
||||
value: ^ref,
|
||||
message: "must be less than or equal to %{less_than_or_equal_to}",
|
||||
vars: %{field: ^opts[:attribute], less_than_or_equal_to: ^value}
|
||||
})
|
||||
)}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def describe(opts) do
|
||||
[
|
||||
|
|
|
@ -105,6 +105,7 @@ defmodule Ash.Test.Actions.UpdateTest do
|
|||
accept([:name])
|
||||
|
||||
validate attribute_equals(:name, "fred")
|
||||
validate compare(:score, greater_than_or_equal_to: 0, less_than_or_equal_to: 10)
|
||||
end
|
||||
|
||||
update :duplicate_name do
|
||||
|
@ -128,6 +129,7 @@ defmodule Ash.Test.Actions.UpdateTest do
|
|||
uuid_primary_key :id
|
||||
attribute :name, :string
|
||||
attribute :bio, :string
|
||||
attribute :score, :integer
|
||||
end
|
||||
|
||||
relationships do
|
||||
|
|
Loading…
Reference in a new issue