mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
docs: add docs for all builtins
This commit is contained in:
parent
0368c1a8da
commit
ac60cda9ac
18 changed files with 297 additions and 70 deletions
|
@ -17,7 +17,7 @@ depending on the details of the request being authorized.
|
|||
|
||||
## Guide
|
||||
|
||||
To see what checks are built-in, see `Ash.Policy.Check.BuiltInChecks`
|
||||
To see what checks are built-in, see `Ash.Policy.Check.Builtins`
|
||||
|
||||
### Basics
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ defmodule Ash.Policy.Authorizer do
|
|||
@bypass
|
||||
],
|
||||
imports: [
|
||||
Ash.Policy.Check.BuiltInChecks,
|
||||
Ash.Policy.Check.Builtins,
|
||||
Ash.Filter.TemplateHelpers
|
||||
],
|
||||
schema: [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Ash.Policy.Check.BuiltInChecks do
|
||||
defmodule Ash.Policy.Check.Builtins do
|
||||
@moduledoc "The global authorization checks built into ash"
|
||||
|
||||
@doc "This check always passes"
|
||||
|
|
|
@ -1185,6 +1185,50 @@ defmodule Ash.Query do
|
|||
do_unload_load(loads, {field, []})
|
||||
end
|
||||
|
||||
@build_opts [
|
||||
filter: [
|
||||
type: :any,
|
||||
doc: "A filter keyword, expression or %Ash.Filter{}"
|
||||
],
|
||||
sort: [
|
||||
type: :any,
|
||||
doc: "A sort list or keyword"
|
||||
],
|
||||
limit: [
|
||||
type: :integer,
|
||||
doc: "A limit to apply"
|
||||
],
|
||||
offset: [
|
||||
type: :integer,
|
||||
doc: "An offset to apply"
|
||||
],
|
||||
load: [
|
||||
type: :any,
|
||||
doc: "A load statement to add to the query"
|
||||
],
|
||||
aggregate: [
|
||||
type: :any,
|
||||
doc:
|
||||
"A custom aggregate to add to the query. Can be `{name, type, relationship}` or `{name, type, relationship, build_opts}`"
|
||||
],
|
||||
calculate: [
|
||||
type: :any,
|
||||
doc:
|
||||
"A custom calculation to add to the query. Can be `{name, module_and_opts}` or `{name, module_and_opts, context}`"
|
||||
],
|
||||
distinct: [
|
||||
type: {:list, :atom},
|
||||
doc: "A distinct clause to add to the query"
|
||||
],
|
||||
context: [
|
||||
type: :map,
|
||||
doc: "A map to merge into the query context"
|
||||
]
|
||||
]
|
||||
|
||||
@doc false
|
||||
def build_opts, do: @build_opts
|
||||
|
||||
@doc """
|
||||
Builds a query from a keyword list.
|
||||
|
||||
|
@ -1208,18 +1252,9 @@ defmodule Ash.Query do
|
|||
Ash.Query.build(Myresource, filter: expr(name == "marge"))
|
||||
```
|
||||
|
||||
Supported keys:
|
||||
* `filter` - filter keyword/expr or `%Ash.Filter{}`
|
||||
* `sort` - sort keyword
|
||||
* `limit` - integer limit
|
||||
* `offset` - integer offset
|
||||
* `load` - keyword/list of atoms to load
|
||||
* `aggregate` - `{name, type, relationship}`
|
||||
* `aggregate` - `{name, type, relationship, query_in_build_format}`
|
||||
* `calculate` - `{name, module_and_opts}`
|
||||
* `calculate` - `{name, module_and_opts, context}`
|
||||
* `distinct` - list of atoms
|
||||
* `context: %{key: value}`
|
||||
## Options
|
||||
|
||||
#{Spark.OptionsHelpers.docs(@build_opts)}
|
||||
"""
|
||||
@spec build(Ash.Resource.t(), Ash.Api.t() | nil, Keyword.t()) :: t()
|
||||
def build(resource, api \\ nil, keyword) do
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
defmodule Ash.Resource.Calculation.Builtins do
|
||||
@moduledoc "Built in calculations that are automatically imported in the calculations section"
|
||||
|
||||
@doc "An example concatenation calculation, that accepts the delimeter as an argument"
|
||||
@doc """
|
||||
An example concatenation calculation, that accepts the delimiter as an argument
|
||||
|
||||
## Examples
|
||||
|
||||
calculate :full_name, concat([:first_name, :last_name], " ")
|
||||
"""
|
||||
@spec concat(keys :: list(atom), separator :: String.t()) :: Ash.Resource.Calculation.ref()
|
||||
def concat(keys, separator \\ "") do
|
||||
{Ash.Resource.Calculation.Concat, keys: keys, separator: separator}
|
||||
end
|
||||
|
|
|
@ -111,6 +111,8 @@ defmodule Ash.Resource.Calculation do
|
|||
allow_nil?: boolean
|
||||
}
|
||||
|
||||
@type ref :: {module(), Keyword.t()} | module()
|
||||
|
||||
defmodule Argument do
|
||||
@moduledoc "An argument to a calculation"
|
||||
defstruct [:name, :type, :default, :allow_nil?, :constraints]
|
||||
|
|
|
@ -7,9 +7,16 @@ defmodule Ash.Resource.Change.Builtins do
|
|||
|
||||
@doc """
|
||||
Relates the actor to the data being changed, as the provided relationship.
|
||||
Accepts the option `:allow_nil?`, which will not force an actor to be set.
|
||||
`:allow_nil?` defaults to `false`.
|
||||
|
||||
## Options
|
||||
|
||||
#{Spark.OptionsHelpers.docs(Ash.Resource.Change.RelateActor.opt_schema())}
|
||||
|
||||
## Examples
|
||||
|
||||
change relate_actor(:owner, allow_nil?: true)
|
||||
"""
|
||||
@spec relate_actor(relationship :: atom, opts :: Keyword.t()) :: Ash.Resource.Change.ref()
|
||||
def relate_actor(relationship, opts \\ []) do
|
||||
opts =
|
||||
opts
|
||||
|
@ -24,20 +31,35 @@ defmodule Ash.Resource.Change.Builtins do
|
|||
|
||||
If a zero argument function is provided, it is called to determine the value.
|
||||
|
||||
If a `arg(:arg_name)` is provided, the value will be read from the argument if supplied.
|
||||
If the argument specified is not given to the action, then nothing happens.
|
||||
Use `arg(:argument_name)` to use the value of the given argument. If the argument is not supplied then nothing happens.
|
||||
|
||||
## Examples
|
||||
|
||||
change set_attribute(:active, false)
|
||||
change set_attribute(:opened_at, &DateTime.utc_now/0)
|
||||
change set_attribute(:status, arg(:status))
|
||||
"""
|
||||
@spec set_attribute(relationship :: atom, (() -> term) | {:_arg, :status} | term()) ::
|
||||
Ash.Resource.Change.ref()
|
||||
def set_attribute(attribute, value) do
|
||||
{Ash.Resource.Change.SetAttribute, attribute: attribute, value: value}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the attribute to the value provided if the attribtue is not already being changed.
|
||||
Sets the attribute to the value provided if the attribute is not already being changed.
|
||||
|
||||
If a zero argument function is provided, it is called to determine the value.
|
||||
|
||||
Use `arg(:argument_name)` to use the value of the given argument. If the argument is not supplied then nothing happens.
|
||||
|
||||
## Examples
|
||||
|
||||
change set_new_attribute(:active, false)
|
||||
change set_new_attribute(:opened_at, &DateTime.utc_now/0)
|
||||
change set_new_attribute(:status, arg(:status))
|
||||
"""
|
||||
@spec set_new_attribute(relationship :: atom, (() -> term) | {:_arg, :status} | term()) ::
|
||||
Ash.Resource.Change.ref()
|
||||
def set_new_attribute(attribute, value) do
|
||||
{Ash.Resource.Change.SetAttribute, attribute: attribute, value: value, new?: true}
|
||||
end
|
||||
|
@ -45,21 +67,37 @@ defmodule Ash.Resource.Change.Builtins do
|
|||
@doc """
|
||||
Clears a change off of the changeset before the action runs.
|
||||
|
||||
Useful if a change is only used in validations but shouldn't ultimately be written to the data layer
|
||||
Does not fail if it is being changed, simply ensures it is cleared just before the action.
|
||||
|
||||
Can be useful if a change is only used in validations but shouldn't ultimately be written to the data layer.
|
||||
|
||||
## Examples
|
||||
|
||||
change prevent_change(:email)
|
||||
"""
|
||||
def prevent_change(field) do
|
||||
{Ash.Resource.Change.PreventChange, field: field}
|
||||
@spec prevent_change(attribute :: atom) :: Ash.Resource.Change.ref()
|
||||
def prevent_change(attribute) do
|
||||
{Ash.Resource.Change.PreventChange, field: attribute}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Calls `Ash.Changeset.manage_relationship/4` with the changeset and relationship provided, using the value provided for the named argument
|
||||
Calls `Ash.Changeset.manage_relationship/4` with the changeset and relationship provided, using the value provided for the named argument.
|
||||
|
||||
For example
|
||||
If relationship_name is not specified, it is assumed to be the same as the argument.
|
||||
|
||||
```elixir
|
||||
change manage_relationship(:add_comments, :comments, on_missing: :ignore, on_match: :no_match, on_no_match: {:create, :add_comment_to_post}
|
||||
```
|
||||
For information on the available options, see `Ash.Changeset.manage_relationship/4`.
|
||||
|
||||
## Examples
|
||||
|
||||
change manage_relationship(:comments, type: :append)
|
||||
change manage_relationship(:remove_comments, :comments, type: :remove)
|
||||
"""
|
||||
@spec manage_relationship(
|
||||
argument :: atom,
|
||||
relationship_name :: atom | nil,
|
||||
opts :: Keyword.t()
|
||||
) ::
|
||||
Ash.Resource.Change.ref()
|
||||
def manage_relationship(argument, relationship_name \\ nil, opts) do
|
||||
relationship_name = relationship_name || argument
|
||||
|
||||
|
@ -68,33 +106,64 @@ defmodule Ash.Resource.Change.Builtins do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Merges the given query context. If an MFA is provided, it will be called with the changeset.
|
||||
Merges the given query context.
|
||||
|
||||
If an MFA is provided, it will be called with the changeset.
|
||||
The MFA should return `{:ok, context_to_be_merged}` or `{:error, term}`
|
||||
|
||||
## Examples
|
||||
|
||||
change set_context(%{something_used_internally: true})
|
||||
change set_context({MyApp.Context, :set_context, []})
|
||||
"""
|
||||
@spec set_context(map | mfa) ::
|
||||
{atom, Keyword.t()}
|
||||
@spec set_context(context :: map | mfa) ::
|
||||
Ash.Resource.Change.ref()
|
||||
def set_context(context) do
|
||||
{Ash.Resource.Change.SetContext, context: context}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Passes the provided value into `changeset.api.load()`, after the action has completed.
|
||||
|
||||
## Example
|
||||
|
||||
change load(:comments)
|
||||
change load([:friend_count, :friends])
|
||||
"""
|
||||
@spec load(load :: term()) :: Ash.Resource.Change.ref()
|
||||
def load(value) do
|
||||
{Ash.Resource.Change.Load, target: value}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Passes the provided value into `Ash.Changeset.select/3`
|
||||
|
||||
Keep in mind, this will *limit* the fields that are selected. You may want `ensure_selected/1` if you
|
||||
want to make sure that something is selected, without deselecting anything else.
|
||||
|
||||
Selecting in changesets does not actually do a select in the data layer, it simply nils out any
|
||||
fields that were not selected. This can be useful if you are writing policies that have to do with
|
||||
specific fields being selected.
|
||||
|
||||
## Example
|
||||
|
||||
change select([:name])
|
||||
"""
|
||||
@spec select(select :: atom | list(atom)) :: Ash.Resource.Change.ref()
|
||||
def select(value) do
|
||||
{Ash.Resource.Change.Select, target: value}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Passes the provided value into `Ash.Changeset.ensure_selected/2`
|
||||
|
||||
If the value is not already selected, this makes sure it is. Does not deselect anything else.
|
||||
|
||||
## Example
|
||||
|
||||
change ensure_selected([:necessary_field])
|
||||
"""
|
||||
@spec ensure_selected(select :: atom | list(atom)) :: Ash.Resource.Change.ref()
|
||||
def ensure_selected(value) do
|
||||
{Ash.Resource.Change.Select, target: value, ensure?: true}
|
||||
end
|
||||
|
|
|
@ -13,6 +13,7 @@ defmodule Ash.Resource.Change do
|
|||
defstruct [:change, :on, :only_when_valid?, :description, where: []]
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
@type ref :: {module(), Keyword.t()} | module()
|
||||
|
||||
@doc false
|
||||
def schema do
|
||||
|
|
|
@ -4,16 +4,28 @@ defmodule Ash.Resource.Change.RelateActor do
|
|||
alias Ash.Changeset
|
||||
alias Ash.Error.Changes.InvalidRelationship
|
||||
|
||||
def init(opts) do
|
||||
case opts[:relationship] do
|
||||
nil ->
|
||||
{:error, "Relationship is required"}
|
||||
@opt_schema [
|
||||
relationship: [
|
||||
doc: "The relationship to set the actor to.",
|
||||
required: true,
|
||||
type: :atom
|
||||
],
|
||||
allow_nil?: [
|
||||
doc: "Wether or not to allow the actor to be nil, in which case nothing will happen.",
|
||||
type: :boolean,
|
||||
default: false
|
||||
]
|
||||
]
|
||||
|
||||
relationship when is_atom(relationship) ->
|
||||
def opt_schema(), do: @opt_schema
|
||||
|
||||
def init(opts) do
|
||||
case Spark.OptionsHelpers.validate(opts, @opt_schema) do
|
||||
{:ok, opts} ->
|
||||
{:ok, opts}
|
||||
|
||||
relationship ->
|
||||
{:error, "Expected an atom for relationship, got: #{inspect(relationship)}"}
|
||||
{:error, error} ->
|
||||
{:error, Exception.message(error)}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,12 +2,18 @@ defmodule Ash.Resource.Preparation.Builtins do
|
|||
@moduledoc "Builtin query preparations"
|
||||
|
||||
@doc """
|
||||
Merges the given query context. If an MFA is provided, it will be called with the query.
|
||||
Merges the given query context.
|
||||
|
||||
If an MFA is provided, it will be called with the changeset.
|
||||
The MFA should return `{:ok, context_to_be_merged}` or `{:error, term}`
|
||||
|
||||
## Examples
|
||||
|
||||
change set_context(%{something_used_internally: true})
|
||||
change set_context({MyApp.Context, :set_context, []})
|
||||
"""
|
||||
@spec set_context(map | (Ash.Query.t() -> mfa)) ::
|
||||
{atom, Keyword.t()}
|
||||
@spec set_context(context :: map | mfa) ::
|
||||
Ash.Resource.Preparation.ref()
|
||||
def set_context(context) do
|
||||
{Ash.Resource.Preparation.SetContext, context: context}
|
||||
end
|
||||
|
@ -15,18 +21,18 @@ defmodule Ash.Resource.Preparation.Builtins do
|
|||
@doc """
|
||||
Passes the given keyword list to `Ash.Query.build/2` with the query being prepared.
|
||||
|
||||
This allows declaring simple query modifications in-line. For more complicated query modifications,
|
||||
use a custom preparation.
|
||||
This allows declaring simple query modifications in-line.
|
||||
|
||||
For example:
|
||||
## Options
|
||||
|
||||
```elixir
|
||||
read :top_ten_songs do
|
||||
prepare build(sort: [song_rank: :desc], limit: 10)
|
||||
end
|
||||
```
|
||||
#{Spark.OptionsHelpers.docs(Ash.Query.build_opts())}
|
||||
|
||||
## Examples
|
||||
|
||||
prepare build(sort: [song_rank: :desc], limit: 10)
|
||||
prepare build(load: [:friends])
|
||||
"""
|
||||
@spec build(Keyword.t()) :: {atom, Keyword.t()}
|
||||
@spec build(Keyword.t()) :: Ash.Resource.Preparation.ref()
|
||||
def build(options) do
|
||||
{Ash.Resource.Preparation.Build, options: options}
|
||||
end
|
||||
|
|
|
@ -16,6 +16,7 @@ defmodule Ash.Resource.Preparation do
|
|||
defstruct [:preparation]
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
@type ref :: {module(), Keyword.t()} | module()
|
||||
|
||||
@doc false
|
||||
def schema do
|
||||
|
|
|
@ -62,6 +62,8 @@ defmodule Ash.Resource.Validation do
|
|||
}
|
||||
|
||||
@type path :: [atom | integer]
|
||||
@type ref :: {module(), Keyword.t()} | module()
|
||||
|
||||
@callback init(Keyword.t()) :: {:ok, Keyword.t()} | {:error, String.t()}
|
||||
@callback validate(Ash.Changeset.t(), Keyword.t()) :: :ok | {:error, term}
|
||||
|
||||
|
|
|
@ -9,60 +9,142 @@ defmodule Ash.Resource.Validation.Builtins do
|
|||
|
||||
@doc """
|
||||
Validates that an attribute's value is in a given list
|
||||
|
||||
## Examples
|
||||
|
||||
validate one_of(:status, [:closed_won, :closed_lost])
|
||||
"""
|
||||
@spec one_of(attribute :: atom, list(any)) :: Validation.ref()
|
||||
def one_of(attribute, values) do
|
||||
{Validation.OneOf, attribute: attribute, values: values}
|
||||
end
|
||||
|
||||
@doc "Validates that an attribute is being changed"
|
||||
@doc """
|
||||
Validates that an attribute or relationship is being changed
|
||||
|
||||
## Examples
|
||||
|
||||
validate changing(:first_name)
|
||||
validate changing(:comments)
|
||||
"""
|
||||
@spec changing(attribute :: atom) :: Validation.ref()
|
||||
def changing(field) do
|
||||
{Validation.Changing, field: field}
|
||||
end
|
||||
|
||||
@doc "Validates that a field or argument matches another field or argument"
|
||||
@doc """
|
||||
Validates that a field or argument matches another field or argument
|
||||
|
||||
## Examples
|
||||
|
||||
validate confirm(:password, :password_confirmation)
|
||||
validate confirm(:email, :email_confirmation)
|
||||
"""
|
||||
@spec confirm(attribute_or_argument :: atom, confirmation_attribute_or_argument :: atom) ::
|
||||
Validation.ref()
|
||||
def confirm(field, confirmation) do
|
||||
{Validation.Confirm, [field: field, confirmation: confirmation]}
|
||||
end
|
||||
|
||||
@doc "Validates that an attribute on the original record does not equal a specific value"
|
||||
@doc """
|
||||
Validates that an attribute is not being changed to a specific value, or does not equal the given value if it is not being changed.
|
||||
|
||||
## Examples
|
||||
|
||||
validate attribute_does_not_equal(:admin, true)
|
||||
|
||||
# Or to only check for changing to a given value
|
||||
validate attribute_does_not_equal(:admin, true), where: [changing(:admin)]
|
||||
"""
|
||||
@spec attribute_does_not_equal(attribute :: atom, value :: term) :: Validation.ref()
|
||||
def attribute_does_not_equal(attribute, value) do
|
||||
{Validation.AttributeDoesNotEqual, attribute: attribute, value: value}
|
||||
end
|
||||
|
||||
@doc "Validates that an attribute on the original record equals a specific value"
|
||||
@doc """
|
||||
Validates that an attribute is being changed to a specific value, or equals the given value if it is not being changed.
|
||||
|
||||
## Examples
|
||||
|
||||
validate attribute_equals(:admin, true)
|
||||
|
||||
# Or to only check for changing to a given value
|
||||
validate attribute_equals(:admin, true), where: [changing(:admin)]
|
||||
"""
|
||||
@spec attribute_equals(attribute :: atom, value :: term) :: Validation.ref()
|
||||
def attribute_equals(attribute, value) do
|
||||
{Validation.AttributeEquals, attribute: attribute, value: value}
|
||||
end
|
||||
|
||||
@doc "Validates that an attribute on the original record meets the given length criteria"
|
||||
@doc """
|
||||
Validates that an attribute on the original record meets the given length criteria
|
||||
|
||||
## Options
|
||||
|
||||
#{Spark.OptionsHelpers.docs(Keyword.delete(Ash.Resource.Validation.StringLength.opt_schema(), :attribute))}
|
||||
|
||||
## Examples
|
||||
|
||||
validate string_length(:slug, exactly: 8)
|
||||
validate string_length(:password, min: 6)
|
||||
validate string_length(:secret, min: 4, max: 12)
|
||||
"""
|
||||
@spec string_length(attribute :: atom, opts :: Keyword.t()) :: Validation.ref()
|
||||
def string_length(attribute, opts \\ []) do
|
||||
{Validation.StringLength, Keyword.merge(opts, attribute: attribute)}
|
||||
end
|
||||
|
||||
@doc "Validates that attribute meets the given criteria"
|
||||
@numericality_docs """
|
||||
Validates that an attribute or argument meets the given comparison criteria.
|
||||
|
||||
## Options
|
||||
#{Spark.OptionsHelpers.docs(Keyword.delete(Ash.Resource.Validation.Compare.opt_schema(), :attribute))}
|
||||
|
||||
## Examples
|
||||
|
||||
validate numericality(:age, greater_than_or_equal_to: 18),
|
||||
where: [attribute_equals(:show_adult_content, true)],
|
||||
message: "Must be over %{greater_than_or_equal_to} to enable adult content."
|
||||
|
||||
validate numericality(:points, greater_than: 0, less_than_or_equal_to: 100)
|
||||
"""
|
||||
@doc @numericality_docs
|
||||
@spec numericality(attribute :: atom, opts :: Keyword.t()) :: Validation.ref()
|
||||
def numericality(attribute, opts \\ []) do
|
||||
compare(attribute, opts)
|
||||
end
|
||||
|
||||
@doc String.replace(@numericality_docs, "numericality(", "compare(")
|
||||
@spec compare(attribute :: atom, opts :: Keyword.t()) :: Validation.ref()
|
||||
def compare(attribute, opts \\ []) do
|
||||
{Validation.Compare, Keyword.merge(opts, attribute: attribute)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validates that an attribute's value matches a given regex or string, using the provided error, message if not.
|
||||
Validates that an attribute's value matches a given regex.
|
||||
|
||||
`String.match?/2` is used to determine if it matches.
|
||||
`String.match?/2` is used to determine if the value matches.
|
||||
|
||||
## Examples
|
||||
|
||||
validate match(:slug, ~r/^[0-9a-z-_]+$/)
|
||||
"""
|
||||
|
||||
def match(attribute, match, message \\ nil) do
|
||||
message = message || "must match #{match}"
|
||||
|
||||
{Validation.Match, attribute: attribute, match: match, message: message}
|
||||
@spec match(attribute :: atom, match :: Regex.t()) :: Validation.ref()
|
||||
def match(attribute, match) do
|
||||
{Validation.Match, attribute: attribute, match: match, message: "must match #{match}"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validates the presence of a list of attributes
|
||||
Validates the presence of a list of attributes or arguments.
|
||||
|
||||
If no options are provided, validates that they are all present.
|
||||
|
||||
## Options
|
||||
|
||||
#{Spark.OptionsHelpers.docs(Keyword.delete(Validation.Present.schema(), :attributes))}
|
||||
"""
|
||||
@spec present(attributes_or_arguments :: atom | list(atom), opts :: Keyword.t()) ::
|
||||
Validation.ref()
|
||||
def present(attributes, opts \\ []) do
|
||||
if opts == [] do
|
||||
attributes = List.wrap(attributes)
|
||||
|
@ -74,12 +156,18 @@ defmodule Ash.Resource.Validation.Builtins do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Validates the absence of a list of attributes
|
||||
Validates the absence of a list of attributes or arguments.
|
||||
|
||||
If no options are provided, validates that they are all absent.
|
||||
|
||||
The docs behave the same as `present/2`, except they validate absence.
|
||||
This works by changing your options and providing them to the `present` validation.
|
||||
|
||||
## Options
|
||||
|
||||
#{String.replace(Spark.OptionsHelpers.docs(Keyword.delete(Validation.Present.schema(), :attributes)), "present", "absent")}
|
||||
"""
|
||||
@spec absent(attributes_or_arguments :: atom | list(atom), opts :: Keyword.t()) ::
|
||||
Validation.ref()
|
||||
def absent(attributes, opts \\ []) do
|
||||
if opts == [] do
|
||||
{Validation.Present, attributes: List.wrap(attributes), exactly: 0}
|
||||
|
|
|
@ -33,6 +33,8 @@ defmodule Ash.Resource.Validation.Compare do
|
|||
]
|
||||
]
|
||||
|
||||
def opt_schema, do: @opt_schema
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
case Spark.OptionsHelpers.validate(opts, @opt_schema) do
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Ash.Resource.Validation.OneOf do
|
|||
|
||||
@opt_schema [
|
||||
values: [
|
||||
type: {:custom, __MODULE__, :values, []},
|
||||
type: {:list, :any},
|
||||
required: true
|
||||
],
|
||||
attribute: [
|
||||
|
|
|
@ -23,6 +23,8 @@ defmodule Ash.Resource.Validation.StringLength do
|
|||
]
|
||||
]
|
||||
|
||||
def opt_schema, do: @opt_schema
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
case Spark.OptionsHelpers.validate(opts, @opt_schema) do
|
||||
|
|
|
@ -116,7 +116,7 @@ defmodule Ash.DocIndex do
|
|||
[
|
||||
Ash.Authorizer,
|
||||
Ash.Policy.Check,
|
||||
Ash.Policy.Check.BuiltInChecks,
|
||||
Ash.Policy.Check.Builtins,
|
||||
Ash.Policy.FilterCheck,
|
||||
Ash.Policy.SimpleCheck
|
||||
]},
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -121,7 +121,7 @@ defmodule Ash.MixProject do
|
|||
Authorization: [
|
||||
Ash.Authorizer,
|
||||
Ash.Policy.Check,
|
||||
Ash.Policy.Check.BuiltInChecks,
|
||||
Ash.Policy.Check.Builtins,
|
||||
Ash.Policy.FilterCheck,
|
||||
Ash.Policy.SimpleCheck
|
||||
],
|
||||
|
|
Loading…
Reference in a new issue