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
|
end
|
||||||
|
|
||||||
%{validation: {module, validation_opts}, where: where}, changeset ->
|
%{validation: {module, validation_opts}, where: where}, changeset ->
|
||||||
with {:atomic, fields, condition_expr, error_expr} <-
|
case List.wrap(module.atomic(changeset, validation_opts)) do
|
||||||
module.atomic(changeset, validation_opts),
|
[{:atomic, _, _, _} | _] = atomics ->
|
||||||
{:changing?, true} <-
|
Enum.reduce_while(atomics, changeset, fn
|
||||||
{:changing?, Enum.any?(fields, &changing_attribute?(changeset, &1))},
|
{:atomic, fields, condition_expr, error_expr}, changeset ->
|
||||||
{:atomic, condition} <- atomic_condition(where, changeset) do
|
with {:changing?, true} <-
|
||||||
case condition do
|
{:changing?,
|
||||||
true ->
|
fields == :* || Enum.any?(fields, &changing_attribute?(changeset, &1))},
|
||||||
{:cont, validate_atomically(changeset, condition_expr, error_expr)}
|
{:atomic, condition} <- atomic_condition(where, changeset) do
|
||||||
|
case condition do
|
||||||
|
true ->
|
||||||
|
{:cont, validate_atomically(changeset, condition_expr, error_expr)}
|
||||||
|
|
||||||
false ->
|
false ->
|
||||||
{:cont, changeset}
|
{:cont, changeset}
|
||||||
|
|
||||||
condition ->
|
condition ->
|
||||||
condition_expr =
|
condition_expr =
|
||||||
Ash.Expr.expr(^condition and condition_expr)
|
Ash.Expr.expr(^condition and condition_expr)
|
||||||
|
|
||||||
{:cont, validate_atomically(changeset, condition_expr, error_expr)}
|
{:cont, validate_atomically(changeset, condition_expr, error_expr)}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
:not_atomic ->
|
{:changing?, false} ->
|
||||||
{:halt, :not_atomic}
|
{:cont, changeset}
|
||||||
|
|
||||||
{:changing?, false} ->
|
:not_atomic ->
|
||||||
{:cont, changeset}
|
{:halt, :not_atomic}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> case do
|
||||||
|
:not_atomic -> {:halt, :not_atomic}
|
||||||
|
changeset -> {:cont, changeset}
|
||||||
|
end
|
||||||
|
|
||||||
|
[value] ->
|
||||||
|
{:halt, value}
|
||||||
end
|
end
|
||||||
end)
|
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.
|
Gets a reference to a field, or the current atomic update expression of that field.
|
||||||
"""
|
"""
|
||||||
def atomic_ref(changeset, field) do
|
def atomic_ref(changeset, field) do
|
||||||
if base_value = changeset.atomics[field] do
|
case Keyword.fetch(changeset.atomics, field) do
|
||||||
%{type: type, constraints: constraints} =
|
{:ok, atomic} ->
|
||||||
Ash.Resource.Info.attribute(changeset.resource, field)
|
%{type: type, constraints: constraints} =
|
||||||
|
Ash.Resource.Info.attribute(changeset.resource, field)
|
||||||
|
|
||||||
Ash.Expr.expr(type(^base_value, ^type, ^constraints))
|
Ash.Expr.expr(type(^atomic, ^type, ^constraints))
|
||||||
else
|
|
||||||
Ash.Expr.expr(ref(^field))
|
: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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -48,8 +48,12 @@ defmodule Ash.Resource.Validation do
|
||||||
@callback atomic?() :: boolean
|
@callback atomic?() :: boolean
|
||||||
@callback atomic(changeset :: Ash.Changeset.t(), opts :: Keyword.t()) ::
|
@callback atomic(changeset :: Ash.Changeset.t(), opts :: Keyword.t()) ::
|
||||||
:ok
|
: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()}
|
error_expr :: Ash.Expr.t()}
|
||||||
|
| [
|
||||||
|
{:atomic, involved_fields :: list(atom) | :*, condition_expr :: Ash.Expr.t(),
|
||||||
|
error_expr :: Ash.Expr.t()}
|
||||||
|
]
|
||||||
| :not_atomic
|
| :not_atomic
|
||||||
| {:error, term()}
|
| {:error, term()}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,11 @@ defmodule Ash.Resource.Validation.ActionIs do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def atomic(changeset, opts) do
|
||||||
|
validate(changeset, opts)
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe(opts) do
|
def describe(opts) do
|
||||||
[message: "must be %{action}", vars: %{action: opts[:action]}]
|
[message: "must be %{action}", vars: %{action: opts[:action]}]
|
||||||
|
|
|
@ -43,6 +43,11 @@ defmodule Ash.Resource.Validation.ArgumentDoesNotEqual do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def atomic(changeset, opts) do
|
||||||
|
validate(changeset, opts)
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe(opts) do
|
def describe(opts) do
|
||||||
[
|
[
|
||||||
|
|
|
@ -42,6 +42,11 @@ defmodule Ash.Resource.Validation.ArgumentEquals do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def atomic(changeset, opts) do
|
||||||
|
validate(changeset, opts)
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe(opts) do
|
def describe(opts) do
|
||||||
[
|
[
|
||||||
|
|
|
@ -42,6 +42,11 @@ defmodule Ash.Resource.Validation.ArgumentIn do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def atomic(changeset, opts) do
|
||||||
|
validate(changeset, opts)
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe(opts) do
|
def describe(opts) do
|
||||||
[
|
[
|
||||||
|
|
|
@ -16,6 +16,7 @@ defmodule Ash.Resource.Validation.AttributeIn do
|
||||||
|
|
||||||
use Ash.Resource.Validation
|
use Ash.Resource.Validation
|
||||||
alias Ash.Error.Changes.InvalidAttribute
|
alias Ash.Error.Changes.InvalidAttribute
|
||||||
|
require Ash.Expr
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init(opts) do
|
def init(opts) do
|
||||||
|
@ -42,11 +43,26 @@ defmodule Ash.Resource.Validation.AttributeIn do
|
||||||
end
|
end
|
||||||
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
|
@impl true
|
||||||
def describe(opts) do
|
def describe(opts) do
|
||||||
[
|
[
|
||||||
message: "must equal %{value}",
|
message: "must be in %{list}",
|
||||||
vars: [field: opts[:attribute], value: opts[:value]]
|
vars: [field: opts[:attribute], list: opts[:list]]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule Ash.Resource.Validation.Changing do
|
||||||
use Ash.Resource.Validation
|
use Ash.Resource.Validation
|
||||||
|
|
||||||
alias Ash.Error.Changes.InvalidAttribute
|
alias Ash.Error.Changes.InvalidAttribute
|
||||||
|
require Ash.Expr
|
||||||
|
|
||||||
@opt_schema [
|
@opt_schema [
|
||||||
field: [
|
field: [
|
||||||
|
@ -51,6 +52,22 @@ defmodule Ash.Resource.Validation.Changing do
|
||||||
end
|
end
|
||||||
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
|
@impl true
|
||||||
def describe(_opts) do
|
def describe(_opts) do
|
||||||
[
|
[
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule Ash.Resource.Validation.Compare do
|
||||||
use Ash.Resource.Validation
|
use Ash.Resource.Validation
|
||||||
|
|
||||||
alias Ash.Error.Changes.InvalidAttribute
|
alias Ash.Error.Changes.InvalidAttribute
|
||||||
|
require Ash.Expr
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init(opts) do
|
def init(opts) do
|
||||||
|
@ -67,6 +68,66 @@ defmodule Ash.Resource.Validation.Compare do
|
||||||
end
|
end
|
||||||
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
|
@impl true
|
||||||
def describe(opts) do
|
def describe(opts) do
|
||||||
[
|
[
|
||||||
|
|
|
@ -105,6 +105,7 @@ defmodule Ash.Test.Actions.UpdateTest do
|
||||||
accept([:name])
|
accept([:name])
|
||||||
|
|
||||||
validate attribute_equals(:name, "fred")
|
validate attribute_equals(:name, "fred")
|
||||||
|
validate compare(:score, greater_than_or_equal_to: 0, less_than_or_equal_to: 10)
|
||||||
end
|
end
|
||||||
|
|
||||||
update :duplicate_name do
|
update :duplicate_name do
|
||||||
|
@ -128,6 +129,7 @@ defmodule Ash.Test.Actions.UpdateTest do
|
||||||
uuid_primary_key :id
|
uuid_primary_key :id
|
||||||
attribute :name, :string
|
attribute :name, :string
|
||||||
attribute :bio, :string
|
attribute :bio, :string
|
||||||
|
attribute :score, :integer
|
||||||
end
|
end
|
||||||
|
|
||||||
relationships do
|
relationships do
|
||||||
|
|
Loading…
Reference in a new issue