mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
improvement: add skip_global_validations?
option for actions
This commit is contained in:
parent
ddf78ca1f2
commit
ed45a72ea6
9 changed files with 133 additions and 14 deletions
|
@ -175,6 +175,7 @@ spark_locals_without_parens = [
|
||||||
sensitive?: 1,
|
sensitive?: 1,
|
||||||
short_name: 1,
|
short_name: 1,
|
||||||
simple_notifiers: 1,
|
simple_notifiers: 1,
|
||||||
|
skip_global_validations?: 1,
|
||||||
soft?: 1,
|
soft?: 1,
|
||||||
sort: 1,
|
sort: 1,
|
||||||
source: 1,
|
source: 1,
|
||||||
|
|
|
@ -192,7 +192,7 @@ The following steps are run when calling `Ash.Changeset.for_create/4`, `Ash.Chan
|
||||||
- Require any accepted attributes that are `allow_nil?` false
|
- Require any accepted attributes that are `allow_nil?` false
|
||||||
- Set any default values for attributes
|
- Set any default values for attributes
|
||||||
- Run action changes & validations
|
- Run action changes & validations
|
||||||
- Run validations, or add them in `before_action` hooks if using `d:Ash.Resource.Dsl.actions.create.validate|before_action?`
|
- Run validations, or add them in `before_action` hooks if using `d:Ash.Resource.Dsl.actions.create.validate|before_action?`. Any global validations are skipped if the action has `skip_global_validations?` set to `true`.
|
||||||
|
|
||||||
#### Running the Create/Update/Destroy Action
|
#### Running the Create/Update/Destroy Action
|
||||||
|
|
||||||
|
|
|
@ -1257,6 +1257,9 @@ defmodule Ash.Changeset do
|
||||||
defp default(:update, %{update_default: value}), do: value
|
defp default(:update, %{update_default: value}), do: value
|
||||||
|
|
||||||
defp add_validations(changeset, tracer, metadata, actor) do
|
defp add_validations(changeset, tracer, metadata, actor) do
|
||||||
|
if changeset.action.skip_global_validations? do
|
||||||
|
changeset
|
||||||
|
else
|
||||||
changeset.resource
|
changeset.resource
|
||||||
# We use the `changeset.action_type` to support soft deletes
|
# We use the `changeset.action_type` to support soft deletes
|
||||||
# Because a delete is an `update` with an action type of `update`
|
# Because a delete is an `update` with an action type of `update`
|
||||||
|
@ -1270,6 +1273,7 @@ defmodule Ash.Changeset do
|
||||||
end)
|
end)
|
||||||
|> Enum.reduce(changeset, &validate(&2, &1, tracer, metadata, actor))
|
|> Enum.reduce(changeset, &validate(&2, &1, tracer, metadata, actor))
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp validate(changeset, validation, tracer, metadata, actor) do
|
defp validate(changeset, validation, tracer, metadata, actor) do
|
||||||
if validation.before_action? do
|
if validation.before_action? do
|
||||||
|
|
|
@ -11,6 +11,7 @@ defmodule Ash.Resource.Actions.Create do
|
||||||
touches_resources: [],
|
touches_resources: [],
|
||||||
require_attributes: [],
|
require_attributes: [],
|
||||||
delay_global_validations?: false,
|
delay_global_validations?: false,
|
||||||
|
skip_global_validations?: false,
|
||||||
upsert?: false,
|
upsert?: false,
|
||||||
upsert_identity: nil,
|
upsert_identity: nil,
|
||||||
arguments: [],
|
arguments: [],
|
||||||
|
@ -29,6 +30,7 @@ defmodule Ash.Resource.Actions.Create do
|
||||||
manual: module | nil,
|
manual: module | nil,
|
||||||
upsert?: boolean,
|
upsert?: boolean,
|
||||||
delay_global_validations?: boolean,
|
delay_global_validations?: boolean,
|
||||||
|
skip_global_validations?: boolean,
|
||||||
upsert_identity: atom | nil,
|
upsert_identity: atom | nil,
|
||||||
allow_nil_input: list(atom),
|
allow_nil_input: list(atom),
|
||||||
touches_resources: list(atom),
|
touches_resources: list(atom),
|
||||||
|
|
|
@ -12,6 +12,7 @@ defmodule Ash.Resource.Actions.Destroy do
|
||||||
arguments: [],
|
arguments: [],
|
||||||
touches_resources: [],
|
touches_resources: [],
|
||||||
delay_global_validations?: false,
|
delay_global_validations?: false,
|
||||||
|
skip_global_validations?: false,
|
||||||
accept: nil,
|
accept: nil,
|
||||||
changes: [],
|
changes: [],
|
||||||
reject: [],
|
reject: [],
|
||||||
|
@ -27,6 +28,7 @@ defmodule Ash.Resource.Actions.Destroy do
|
||||||
manual: module | nil,
|
manual: module | nil,
|
||||||
arguments: list(Ash.Resource.Actions.Argument.t()),
|
arguments: list(Ash.Resource.Actions.Argument.t()),
|
||||||
delay_global_validations?: boolean,
|
delay_global_validations?: boolean,
|
||||||
|
skip_global_validations?: boolean,
|
||||||
touches_resources: list(atom),
|
touches_resources: list(atom),
|
||||||
primary?: boolean,
|
primary?: boolean,
|
||||||
description: String.t()
|
description: String.t()
|
||||||
|
|
|
@ -45,6 +45,13 @@ defmodule Ash.Resource.Actions.SharedOptions do
|
||||||
on the resource.
|
on the resource.
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
|
skip_global_validations?: [
|
||||||
|
type: :boolean,
|
||||||
|
default: false,
|
||||||
|
doc: """
|
||||||
|
If true, global validations will be skipped. Useful for manual actions.
|
||||||
|
"""
|
||||||
|
],
|
||||||
reject: [
|
reject: [
|
||||||
type: {:or, [in: [:all], list: :atom]},
|
type: {:or, [in: [:all], list: :atom]},
|
||||||
doc: """
|
doc: """
|
||||||
|
|
|
@ -11,6 +11,7 @@ defmodule Ash.Resource.Actions.Update do
|
||||||
manual?: false,
|
manual?: false,
|
||||||
require_attributes: [],
|
require_attributes: [],
|
||||||
delay_global_validations?: false,
|
delay_global_validations?: false,
|
||||||
|
skip_global_validations?: false,
|
||||||
arguments: [],
|
arguments: [],
|
||||||
changes: [],
|
changes: [],
|
||||||
reject: [],
|
reject: [],
|
||||||
|
@ -27,6 +28,7 @@ defmodule Ash.Resource.Actions.Update do
|
||||||
accept: list(atom),
|
accept: list(atom),
|
||||||
arguments: list(Ash.Resource.Actions.Argument.t()),
|
arguments: list(Ash.Resource.Actions.Argument.t()),
|
||||||
delay_global_validations?: boolean,
|
delay_global_validations?: boolean,
|
||||||
|
skip_global_validations?: boolean,
|
||||||
primary?: boolean,
|
primary?: boolean,
|
||||||
touches_resources: list(atom),
|
touches_resources: list(atom),
|
||||||
description: String.t()
|
description: String.t()
|
||||||
|
|
|
@ -98,7 +98,7 @@ defmodule Ash.Resource.Builder do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Builds an action
|
Builds a relationship
|
||||||
"""
|
"""
|
||||||
@spec build_relationship(
|
@spec build_relationship(
|
||||||
type :: Ash.Resource.Relationships.type(),
|
type :: Ash.Resource.Relationships.type(),
|
||||||
|
@ -118,6 +118,60 @@ defmodule Ash.Resource.Builder do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Builds and adds a new identity unless an identity with that name already exists
|
||||||
|
"""
|
||||||
|
@spec add_new_identity(
|
||||||
|
Spark.Dsl.Builder.input(),
|
||||||
|
name :: atom,
|
||||||
|
fields :: atom | list(atom),
|
||||||
|
opts :: Keyword.t()
|
||||||
|
) ::
|
||||||
|
Spark.Dsl.Builder.result()
|
||||||
|
defbuilder add_new_identity(dsl_state, name, fields, opts \\ []) do
|
||||||
|
if Ash.Resource.Info.identity(dsl_state, name) do
|
||||||
|
dsl_state
|
||||||
|
else
|
||||||
|
add_identity(dsl_state, name, fields, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Builds and adds an identity
|
||||||
|
"""
|
||||||
|
@spec add_identity(
|
||||||
|
Spark.Dsl.Builder.input(),
|
||||||
|
name :: atom,
|
||||||
|
fields :: atom | list(atom),
|
||||||
|
opts :: Keyword.t()
|
||||||
|
) ::
|
||||||
|
Spark.Dsl.Builder.result()
|
||||||
|
defbuilder add_identity(dsl_state, name, fields, opts \\ []) do
|
||||||
|
with {:ok, identity} <- build_identity(name, fields, opts) do
|
||||||
|
Transformer.add_entity(dsl_state, [:identities], identity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Builds an action
|
||||||
|
"""
|
||||||
|
@spec build_relationship(
|
||||||
|
name :: atom,
|
||||||
|
fields :: atom | list(atom),
|
||||||
|
opts :: Keyword.t()
|
||||||
|
) ::
|
||||||
|
{:ok, Ash.Resource.Relationships.relationship()} | {:error, term}
|
||||||
|
def build_identity(name, fields, opts \\ []) do
|
||||||
|
with {:ok, opts} <- handle_nested_builders(opts, [:changes, :arguments, :metadata]) do
|
||||||
|
Transformer.build_entity(
|
||||||
|
Ash.Resource.Dsl,
|
||||||
|
[:identities],
|
||||||
|
:identity,
|
||||||
|
Keyword.merge(opts, name: name, keys: fields)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Builds and adds a change
|
Builds and adds a change
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -304,6 +304,40 @@ defmodule Ash.Test.Actions.CreateTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmodule GlobalValidation do
|
||||||
|
@moduledoc false
|
||||||
|
use Ash.Resource,
|
||||||
|
data_layer: Ash.DataLayer.Ets
|
||||||
|
|
||||||
|
ets do
|
||||||
|
private?(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
actions do
|
||||||
|
defaults [:create, :read, :update, :destroy]
|
||||||
|
|
||||||
|
create :manual do
|
||||||
|
skip_global_validations?(true)
|
||||||
|
|
||||||
|
manual fn changeset, _ ->
|
||||||
|
Ash.Changeset.apply_attributes(changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
validations do
|
||||||
|
validate compare(:foo, greater_than: 10)
|
||||||
|
end
|
||||||
|
|
||||||
|
attributes do
|
||||||
|
uuid_primary_key :id
|
||||||
|
|
||||||
|
attribute :foo, :integer do
|
||||||
|
allow_nil? false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defmodule Registry do
|
defmodule Registry do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
use Ash.Registry
|
use Ash.Registry
|
||||||
|
@ -317,6 +351,7 @@ defmodule Ash.Test.Actions.CreateTest do
|
||||||
entry(PostLink)
|
entry(PostLink)
|
||||||
entry(Authorized)
|
entry(Authorized)
|
||||||
entry(GeneratedPkey)
|
entry(GeneratedPkey)
|
||||||
|
entry(GlobalValidation)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -903,4 +938,16 @@ defmodule Ash.Test.Actions.CreateTest do
|
||||||
assert [] = Api.read!(Authorized)
|
assert [] = Api.read!(Authorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "global validations" do
|
||||||
|
test "they can be skipped" do
|
||||||
|
assert %{errors: [%Ash.Error.Changes.InvalidAttribute{field: :foo}]} =
|
||||||
|
GlobalValidation
|
||||||
|
|> Ash.Changeset.for_create(:create, %{foo: 5})
|
||||||
|
|
||||||
|
assert %{errors: []} =
|
||||||
|
GlobalValidation
|
||||||
|
|> Ash.Changeset.for_create(:manual, %{foo: 5})
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue