mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 13:33:20 +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
|
## 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
|
### Basics
|
||||||
|
|
||||||
|
|
|
@ -225,7 +225,7 @@ defmodule Ash.Policy.Authorizer do
|
||||||
@bypass
|
@bypass
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
Ash.Policy.Check.BuiltInChecks,
|
Ash.Policy.Check.Builtins,
|
||||||
Ash.Filter.TemplateHelpers
|
Ash.Filter.TemplateHelpers
|
||||||
],
|
],
|
||||||
schema: [
|
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"
|
@moduledoc "The global authorization checks built into ash"
|
||||||
|
|
||||||
@doc "This check always passes"
|
@doc "This check always passes"
|
||||||
|
|
|
@ -1185,6 +1185,50 @@ defmodule Ash.Query do
|
||||||
do_unload_load(loads, {field, []})
|
do_unload_load(loads, {field, []})
|
||||||
end
|
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 """
|
@doc """
|
||||||
Builds a query from a keyword list.
|
Builds a query from a keyword list.
|
||||||
|
|
||||||
|
@ -1208,18 +1252,9 @@ defmodule Ash.Query do
|
||||||
Ash.Query.build(Myresource, filter: expr(name == "marge"))
|
Ash.Query.build(Myresource, filter: expr(name == "marge"))
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported keys:
|
## Options
|
||||||
* `filter` - filter keyword/expr or `%Ash.Filter{}`
|
|
||||||
* `sort` - sort keyword
|
#{Spark.OptionsHelpers.docs(@build_opts)}
|
||||||
* `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}`
|
|
||||||
"""
|
"""
|
||||||
@spec build(Ash.Resource.t(), Ash.Api.t() | nil, Keyword.t()) :: t()
|
@spec build(Ash.Resource.t(), Ash.Api.t() | nil, Keyword.t()) :: t()
|
||||||
def build(resource, api \\ nil, keyword) do
|
def build(resource, api \\ nil, keyword) do
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
defmodule Ash.Resource.Calculation.Builtins do
|
defmodule Ash.Resource.Calculation.Builtins do
|
||||||
@moduledoc "Built in calculations that are automatically imported in the calculations section"
|
@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
|
def concat(keys, separator \\ "") do
|
||||||
{Ash.Resource.Calculation.Concat, keys: keys, separator: separator}
|
{Ash.Resource.Calculation.Concat, keys: keys, separator: separator}
|
||||||
end
|
end
|
||||||
|
|
|
@ -111,6 +111,8 @@ defmodule Ash.Resource.Calculation do
|
||||||
allow_nil?: boolean
|
allow_nil?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@type ref :: {module(), Keyword.t()} | module()
|
||||||
|
|
||||||
defmodule Argument do
|
defmodule Argument do
|
||||||
@moduledoc "An argument to a calculation"
|
@moduledoc "An argument to a calculation"
|
||||||
defstruct [:name, :type, :default, :allow_nil?, :constraints]
|
defstruct [:name, :type, :default, :allow_nil?, :constraints]
|
||||||
|
|
|
@ -7,9 +7,16 @@ defmodule Ash.Resource.Change.Builtins do
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Relates the actor to the data being changed, as the provided relationship.
|
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
|
def relate_actor(relationship, opts \\ []) do
|
||||||
opts =
|
opts =
|
||||||
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 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.
|
Use `arg(:argument_name)` to use the value of the given argument. If the argument is not supplied then nothing happens.
|
||||||
If the argument specified is not given to the action, 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
|
def set_attribute(attribute, value) do
|
||||||
{Ash.Resource.Change.SetAttribute, attribute: attribute, value: value}
|
{Ash.Resource.Change.SetAttribute, attribute: attribute, value: value}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@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.
|
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.
|
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
|
def set_new_attribute(attribute, value) do
|
||||||
{Ash.Resource.Change.SetAttribute, attribute: attribute, value: value, new?: true}
|
{Ash.Resource.Change.SetAttribute, attribute: attribute, value: value, new?: true}
|
||||||
end
|
end
|
||||||
|
@ -45,21 +67,37 @@ defmodule Ash.Resource.Change.Builtins do
|
||||||
@doc """
|
@doc """
|
||||||
Clears a change off of the changeset before the action runs.
|
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
|
@spec prevent_change(attribute :: atom) :: Ash.Resource.Change.ref()
|
||||||
{Ash.Resource.Change.PreventChange, field: field}
|
def prevent_change(attribute) do
|
||||||
|
{Ash.Resource.Change.PreventChange, field: attribute}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@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
|
For information on the available options, see `Ash.Changeset.manage_relationship/4`.
|
||||||
change manage_relationship(:add_comments, :comments, on_missing: :ignore, on_match: :no_match, on_no_match: {:create, :add_comment_to_post}
|
|
||||||
```
|
## 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
|
def manage_relationship(argument, relationship_name \\ nil, opts) do
|
||||||
relationship_name = relationship_name || argument
|
relationship_name = relationship_name || argument
|
||||||
|
|
||||||
|
@ -68,33 +106,64 @@ defmodule Ash.Resource.Change.Builtins do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@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}`
|
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) ::
|
@spec set_context(context :: map | mfa) ::
|
||||||
{atom, Keyword.t()}
|
Ash.Resource.Change.ref()
|
||||||
def set_context(context) do
|
def set_context(context) do
|
||||||
{Ash.Resource.Change.SetContext, context: context}
|
{Ash.Resource.Change.SetContext, context: context}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Passes the provided value into `changeset.api.load()`, after the action has completed.
|
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
|
def load(value) do
|
||||||
{Ash.Resource.Change.Load, target: value}
|
{Ash.Resource.Change.Load, target: value}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Passes the provided value into `Ash.Changeset.select/3`
|
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
|
def select(value) do
|
||||||
{Ash.Resource.Change.Select, target: value}
|
{Ash.Resource.Change.Select, target: value}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Passes the provided value into `Ash.Changeset.ensure_selected/2`
|
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
|
def ensure_selected(value) do
|
||||||
{Ash.Resource.Change.Select, target: value, ensure?: true}
|
{Ash.Resource.Change.Select, target: value, ensure?: true}
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,6 +13,7 @@ defmodule Ash.Resource.Change do
|
||||||
defstruct [:change, :on, :only_when_valid?, :description, where: []]
|
defstruct [:change, :on, :only_when_valid?, :description, where: []]
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
@type ref :: {module(), Keyword.t()} | module()
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def schema do
|
def schema do
|
||||||
|
|
|
@ -4,16 +4,28 @@ defmodule Ash.Resource.Change.RelateActor do
|
||||||
alias Ash.Changeset
|
alias Ash.Changeset
|
||||||
alias Ash.Error.Changes.InvalidRelationship
|
alias Ash.Error.Changes.InvalidRelationship
|
||||||
|
|
||||||
def init(opts) do
|
@opt_schema [
|
||||||
case opts[:relationship] do
|
relationship: [
|
||||||
nil ->
|
doc: "The relationship to set the actor to.",
|
||||||
{:error, "Relationship is required"}
|
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}
|
{:ok, opts}
|
||||||
|
|
||||||
relationship ->
|
{:error, error} ->
|
||||||
{:error, "Expected an atom for relationship, got: #{inspect(relationship)}"}
|
{:error, Exception.message(error)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,18 @@ defmodule Ash.Resource.Preparation.Builtins do
|
||||||
@moduledoc "Builtin query preparations"
|
@moduledoc "Builtin query preparations"
|
||||||
|
|
||||||
@doc """
|
@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}`
|
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)) ::
|
@spec set_context(context :: map | mfa) ::
|
||||||
{atom, Keyword.t()}
|
Ash.Resource.Preparation.ref()
|
||||||
def set_context(context) do
|
def set_context(context) do
|
||||||
{Ash.Resource.Preparation.SetContext, context: context}
|
{Ash.Resource.Preparation.SetContext, context: context}
|
||||||
end
|
end
|
||||||
|
@ -15,18 +21,18 @@ defmodule Ash.Resource.Preparation.Builtins do
|
||||||
@doc """
|
@doc """
|
||||||
Passes the given keyword list to `Ash.Query.build/2` with the query being prepared.
|
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,
|
This allows declaring simple query modifications in-line.
|
||||||
use a custom preparation.
|
|
||||||
|
|
||||||
For example:
|
## Options
|
||||||
|
|
||||||
|
#{Spark.OptionsHelpers.docs(Ash.Query.build_opts())}
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
```elixir
|
|
||||||
read :top_ten_songs do
|
|
||||||
prepare build(sort: [song_rank: :desc], limit: 10)
|
prepare build(sort: [song_rank: :desc], limit: 10)
|
||||||
end
|
prepare build(load: [:friends])
|
||||||
```
|
|
||||||
"""
|
"""
|
||||||
@spec build(Keyword.t()) :: {atom, Keyword.t()}
|
@spec build(Keyword.t()) :: Ash.Resource.Preparation.ref()
|
||||||
def build(options) do
|
def build(options) do
|
||||||
{Ash.Resource.Preparation.Build, options: options}
|
{Ash.Resource.Preparation.Build, options: options}
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,6 +16,7 @@ defmodule Ash.Resource.Preparation do
|
||||||
defstruct [:preparation]
|
defstruct [:preparation]
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
@type ref :: {module(), Keyword.t()} | module()
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def schema do
|
def schema do
|
||||||
|
|
|
@ -62,6 +62,8 @@ defmodule Ash.Resource.Validation do
|
||||||
}
|
}
|
||||||
|
|
||||||
@type path :: [atom | integer]
|
@type path :: [atom | integer]
|
||||||
|
@type ref :: {module(), Keyword.t()} | module()
|
||||||
|
|
||||||
@callback init(Keyword.t()) :: {:ok, Keyword.t()} | {:error, String.t()}
|
@callback init(Keyword.t()) :: {:ok, Keyword.t()} | {:error, String.t()}
|
||||||
@callback validate(Ash.Changeset.t(), Keyword.t()) :: :ok | {:error, term}
|
@callback validate(Ash.Changeset.t(), Keyword.t()) :: :ok | {:error, term}
|
||||||
|
|
||||||
|
|
|
@ -9,60 +9,142 @@ defmodule Ash.Resource.Validation.Builtins do
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Validates that an attribute's value is in a given list
|
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
|
def one_of(attribute, values) do
|
||||||
{Validation.OneOf, attribute: attribute, values: values}
|
{Validation.OneOf, attribute: attribute, values: values}
|
||||||
end
|
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
|
def changing(field) do
|
||||||
{Validation.Changing, field: field}
|
{Validation.Changing, field: field}
|
||||||
end
|
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
|
def confirm(field, confirmation) do
|
||||||
{Validation.Confirm, [field: field, confirmation: confirmation]}
|
{Validation.Confirm, [field: field, confirmation: confirmation]}
|
||||||
end
|
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
|
def attribute_does_not_equal(attribute, value) do
|
||||||
{Validation.AttributeDoesNotEqual, attribute: attribute, value: value}
|
{Validation.AttributeDoesNotEqual, attribute: attribute, value: value}
|
||||||
end
|
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
|
def attribute_equals(attribute, value) do
|
||||||
{Validation.AttributeEquals, attribute: attribute, value: value}
|
{Validation.AttributeEquals, attribute: attribute, value: value}
|
||||||
end
|
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
|
def string_length(attribute, opts \\ []) do
|
||||||
{Validation.StringLength, Keyword.merge(opts, attribute: attribute)}
|
{Validation.StringLength, Keyword.merge(opts, attribute: attribute)}
|
||||||
end
|
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
|
def compare(attribute, opts \\ []) do
|
||||||
{Validation.Compare, Keyword.merge(opts, attribute: attribute)}
|
{Validation.Compare, Keyword.merge(opts, attribute: attribute)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@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-_]+$/)
|
||||||
"""
|
"""
|
||||||
|
@spec match(attribute :: atom, match :: Regex.t()) :: Validation.ref()
|
||||||
def match(attribute, match, message \\ nil) do
|
def match(attribute, match) do
|
||||||
message = message || "must match #{match}"
|
{Validation.Match, attribute: attribute, match: match, message: "must match #{match}"}
|
||||||
|
|
||||||
{Validation.Match, attribute: attribute, match: match, message: message}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@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.
|
If no options are provided, validates that they are all present.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
#{Spark.OptionsHelpers.docs(Keyword.delete(Validation.Present.schema(), :attributes))}
|
#{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
|
def present(attributes, opts \\ []) do
|
||||||
if opts == [] do
|
if opts == [] do
|
||||||
attributes = List.wrap(attributes)
|
attributes = List.wrap(attributes)
|
||||||
|
@ -74,12 +156,18 @@ defmodule Ash.Resource.Validation.Builtins do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@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.
|
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
|
def absent(attributes, opts \\ []) do
|
||||||
if opts == [] do
|
if opts == [] do
|
||||||
{Validation.Present, attributes: List.wrap(attributes), exactly: 0}
|
{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
|
@impl true
|
||||||
def init(opts) do
|
def init(opts) do
|
||||||
case Spark.OptionsHelpers.validate(opts, @opt_schema) do
|
case Spark.OptionsHelpers.validate(opts, @opt_schema) do
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Ash.Resource.Validation.OneOf do
|
||||||
|
|
||||||
@opt_schema [
|
@opt_schema [
|
||||||
values: [
|
values: [
|
||||||
type: {:custom, __MODULE__, :values, []},
|
type: {:list, :any},
|
||||||
required: true
|
required: true
|
||||||
],
|
],
|
||||||
attribute: [
|
attribute: [
|
||||||
|
|
|
@ -23,6 +23,8 @@ defmodule Ash.Resource.Validation.StringLength do
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def opt_schema, do: @opt_schema
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init(opts) do
|
def init(opts) do
|
||||||
case Spark.OptionsHelpers.validate(opts, @opt_schema) do
|
case Spark.OptionsHelpers.validate(opts, @opt_schema) do
|
||||||
|
|
|
@ -116,7 +116,7 @@ defmodule Ash.DocIndex do
|
||||||
[
|
[
|
||||||
Ash.Authorizer,
|
Ash.Authorizer,
|
||||||
Ash.Policy.Check,
|
Ash.Policy.Check,
|
||||||
Ash.Policy.Check.BuiltInChecks,
|
Ash.Policy.Check.Builtins,
|
||||||
Ash.Policy.FilterCheck,
|
Ash.Policy.FilterCheck,
|
||||||
Ash.Policy.SimpleCheck
|
Ash.Policy.SimpleCheck
|
||||||
]},
|
]},
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -121,7 +121,7 @@ defmodule Ash.MixProject do
|
||||||
Authorization: [
|
Authorization: [
|
||||||
Ash.Authorizer,
|
Ash.Authorizer,
|
||||||
Ash.Policy.Check,
|
Ash.Policy.Check,
|
||||||
Ash.Policy.Check.BuiltInChecks,
|
Ash.Policy.Check.Builtins,
|
||||||
Ash.Policy.FilterCheck,
|
Ash.Policy.FilterCheck,
|
||||||
Ash.Policy.SimpleCheck
|
Ash.Policy.SimpleCheck
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in a new issue