mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
docs: update documentation
This commit is contained in:
parent
bf1fafef3c
commit
797792f947
8 changed files with 87 additions and 64 deletions
90
lib/ash.ex
90
lib/ash.ex
|
@ -2,11 +2,9 @@ defmodule Ash do
|
|||
@moduledoc """
|
||||
The primary interface for interrogating apis and resources.
|
||||
|
||||
This is not the code level interface for a resource. Instead, call functions
|
||||
on an `Api` module that contains those resources. This is for retrieving
|
||||
resource/api configurations.
|
||||
These are tools for interrogating resources to derive behavior based on their
|
||||
configuration. This is how all of the behavior of Ash is ultimately configured.
|
||||
"""
|
||||
alias Ash.Error
|
||||
alias Ash.Resource.Actions.{Create, Destroy, Read, Update}
|
||||
alias Ash.Resource.Relationships.{BelongsTo, HasMany, HasOne, ManyToMany}
|
||||
|
||||
|
@ -32,77 +30,31 @@ defmodule Ash do
|
|||
@type query :: Ash.Query.t()
|
||||
@type actor :: Ash.record()
|
||||
|
||||
defmacro partial_resource(do: body) do
|
||||
quote do
|
||||
defmacro __using__(_) do
|
||||
body = unquote(body)
|
||||
|
||||
quote do
|
||||
unquote(body)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def ash_error?(value) do
|
||||
!!Ash.Error.impl_for(value)
|
||||
end
|
||||
|
||||
def to_ash_error(values) when is_list(values) do
|
||||
values =
|
||||
Enum.map(values, fn value ->
|
||||
if ash_error?(value) do
|
||||
value
|
||||
else
|
||||
Error.Unknown.exception(error: values)
|
||||
end
|
||||
end)
|
||||
|
||||
Error.choose_error(values)
|
||||
end
|
||||
|
||||
def to_ash_error(value) do
|
||||
to_ash_error([value])
|
||||
end
|
||||
|
||||
@doc "A short description of the resource, to be included in autogenerated documentation"
|
||||
@spec describe(resource()) :: String.t()
|
||||
def describe(resource) do
|
||||
resource.describe()
|
||||
end
|
||||
|
||||
@doc "A list of authorizers to be used when accessing the resource"
|
||||
@spec authorizers(resource()) :: [module]
|
||||
def authorizers(resource) do
|
||||
resource.authorizers()
|
||||
end
|
||||
|
||||
@spec resource_module?(module) :: boolean
|
||||
def resource_module?(module) do
|
||||
:attributes
|
||||
|> module.module_info()
|
||||
|> Keyword.get(:behaviour, [])
|
||||
|> Enum.any?(&(&1 == Ash.Resource))
|
||||
end
|
||||
|
||||
@spec data_layer_can?(resource(), Ash.DataLayer.feature()) :: boolean
|
||||
def data_layer_can?(resource, feature) do
|
||||
data_layer = data_layer(resource)
|
||||
|
||||
data_layer && data_layer.can?(resource, feature)
|
||||
end
|
||||
|
||||
@spec data_layer_filters(resource) :: map
|
||||
def data_layer_filters(resource) do
|
||||
Ash.DataLayer.custom_filters(resource)
|
||||
end
|
||||
|
||||
@doc "A list of resource modules for a given API"
|
||||
@spec resources(api) :: list(resource())
|
||||
def resources(api) do
|
||||
api.resources()
|
||||
end
|
||||
|
||||
@doc "A list of field names corresponding to the primary key of a resource"
|
||||
@spec primary_key(resource()) :: list(atom)
|
||||
def primary_key(resource) do
|
||||
resource.primary_key()
|
||||
end
|
||||
|
||||
@doc "Gets a relationship by name from the resource"
|
||||
@spec relationship(resource(), atom() | String.t()) :: relationship() | nil
|
||||
def relationship(resource, relationship_name) when is_bitstring(relationship_name) do
|
||||
Enum.find(resource.relationships(), &(to_string(&1.name) == relationship_name))
|
||||
|
@ -112,11 +64,13 @@ defmodule Ash do
|
|||
Enum.find(resource.relationships(), &(&1.name == relationship_name))
|
||||
end
|
||||
|
||||
@doc "A list of relationships on the resource"
|
||||
@spec relationships(resource()) :: list(relationship())
|
||||
def relationships(resource) do
|
||||
resource.relationships()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def primary_action!(resource, type) do
|
||||
case primary_action(resource, type) do
|
||||
nil -> raise "Required primary #{type} action for #{inspect(resource)}"
|
||||
|
@ -124,6 +78,7 @@ defmodule Ash do
|
|||
end
|
||||
end
|
||||
|
||||
@doc "Returns the primary action of a given type for a resource"
|
||||
@spec primary_action(resource(), atom()) :: action() | nil
|
||||
def primary_action(resource, type) do
|
||||
resource
|
||||
|
@ -135,16 +90,19 @@ defmodule Ash do
|
|||
end
|
||||
end
|
||||
|
||||
@doc "Returns the action with the matching name and type on the resource"
|
||||
@spec action(resource(), atom(), atom()) :: action() | nil
|
||||
def action(resource, name, type) do
|
||||
Enum.find(resource.actions(), &(&1.name == name && &1.type == type))
|
||||
end
|
||||
|
||||
@doc "A list of all actions on the resource"
|
||||
@spec actions(resource()) :: list(action())
|
||||
def actions(resource) do
|
||||
resource.actions()
|
||||
end
|
||||
|
||||
@doc "Get an attribute name from the resource"
|
||||
@spec attribute(resource(), String.t() | atom) :: attribute() | nil
|
||||
def attribute(resource, name) when is_bitstring(name) do
|
||||
Enum.find(resource.attributes, &(to_string(&1.name) == name))
|
||||
|
@ -154,23 +112,41 @@ defmodule Ash do
|
|||
Enum.find(resource.attributes, &(&1.name == name))
|
||||
end
|
||||
|
||||
@doc "A list of all attributes on the resource"
|
||||
@spec attributes(resource()) :: list(attribute())
|
||||
def attributes(resource) do
|
||||
resource.attributes()
|
||||
end
|
||||
|
||||
@doc "The name of the resource, e.g 'posts'"
|
||||
@spec name(resource()) :: String.t()
|
||||
def name(resource) do
|
||||
resource.name()
|
||||
end
|
||||
|
||||
@doc "The type of the resource, e.g 'post'"
|
||||
@spec type(resource()) :: String.t()
|
||||
def type(resource) do
|
||||
resource.type()
|
||||
end
|
||||
|
||||
@doc "The data layer of the resource, or nil if it does not have one"
|
||||
@spec data_layer(resource()) :: data_layer()
|
||||
def data_layer(resource) do
|
||||
resource.data_layer()
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec data_layer_can?(resource(), Ash.DataLayer.feature()) :: boolean
|
||||
def data_layer_can?(resource, feature) do
|
||||
data_layer = data_layer(resource)
|
||||
|
||||
data_layer && Ash.DataLayer.can?(feature, resource)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec data_layer_filters(resource) :: map
|
||||
def data_layer_filters(resource) do
|
||||
Ash.DataLayer.custom_filters(resource)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -227,7 +227,7 @@ defmodule Ash.Actions.Relationships do
|
|||
!is_map(data) ->
|
||||
validate_map_replace(relationship, data)
|
||||
|
||||
Map.get(data, :__struct__) && Ash.resource_module?(data.__struct__) ->
|
||||
Map.get(data, :__struct__) ->
|
||||
validate_struct_replace(relationship, data, action_type)
|
||||
|
||||
true ->
|
||||
|
|
|
@ -9,9 +9,12 @@ defmodule Ash.DataLayer.Ets do
|
|||
|
||||
alias Ash.Filter.{And, Eq, In, NotEq, NotIn, Or}
|
||||
|
||||
@callback ets_private?() :: boolean
|
||||
|
||||
defmacro __using__(opts) do
|
||||
quote bind_quoted: [opts: opts] do
|
||||
@data_layer Ash.DataLayer.Ets
|
||||
@behaviour Ash.DataLayer.Ets
|
||||
|
||||
@ets_private? Keyword.get(opts, :private?, false)
|
||||
|
||||
|
@ -21,6 +24,7 @@ defmodule Ash.DataLayer.Ets do
|
|||
end
|
||||
end
|
||||
|
||||
@spec private?(Ash.resource()) :: boolean
|
||||
def private?(resource) do
|
||||
resource.ets_private?()
|
||||
end
|
||||
|
@ -31,7 +35,6 @@ defmodule Ash.DataLayer.Ets do
|
|||
end
|
||||
|
||||
@impl true
|
||||
|
||||
def can?(resource, :async_engine) do
|
||||
not private?(resource)
|
||||
end
|
||||
|
|
|
@ -336,7 +336,7 @@ defmodule Ash.Engine do
|
|||
end
|
||||
|
||||
defp to_ash_error(error) do
|
||||
if Ash.ash_error?(error) do
|
||||
if Ash.Error.ash_error?(error) do
|
||||
error
|
||||
else
|
||||
Unknown.exception(error: error)
|
||||
|
|
|
@ -422,7 +422,7 @@ defmodule Ash.Engine.Runner do
|
|||
end
|
||||
|
||||
defp to_ash_error(error) do
|
||||
if Ash.ash_error?(error) do
|
||||
if Ash.Error.ash_error?(error) do
|
||||
error
|
||||
else
|
||||
Unknown.exception(error: error)
|
||||
|
|
|
@ -21,6 +21,27 @@ defmodule Ash.Error do
|
|||
|
||||
@error_class_indices @error_classes |> Enum.with_index() |> Enum.into(%{})
|
||||
|
||||
def ash_error?(value) do
|
||||
!!impl_for(value)
|
||||
end
|
||||
|
||||
def to_ash_error(values) when is_list(values) do
|
||||
values =
|
||||
Enum.map(values, fn value ->
|
||||
if ash_error?(value) do
|
||||
value
|
||||
else
|
||||
Error.Unknown.exception(error: values)
|
||||
end
|
||||
end)
|
||||
|
||||
Error.choose_error(values)
|
||||
end
|
||||
|
||||
def to_ash_error(value) do
|
||||
to_ash_error([value])
|
||||
end
|
||||
|
||||
def choose_error(errors) do
|
||||
[error | _other_errors] = Enum.sort_by(errors, &Map.get(@error_class_indices, &1.class))
|
||||
|
||||
|
|
|
@ -18,14 +18,36 @@ defmodule Ash.Resource do
|
|||
|
||||
Resource DSL documentation: `Ash.Resource.DSL`
|
||||
|
||||
The following options apply to `use Ash.Resource, [...]`
|
||||
#{NimbleOptions.docs(@resource_opts_schema)}
|
||||
|
||||
For more information on the resource DSL, see `Ash.Resource.DSL`
|
||||
|
||||
Note:
|
||||
*Do not* call the functions on a resource, as in `MyResource.type()` as this is a *private*
|
||||
API and can change at any time. Instead, use the `Ash` module, for example: `Ash.type(MyResource)`
|
||||
"""
|
||||
|
||||
@doc "The name of the resource, e.g 'posts'"
|
||||
@callback name() :: String.t()
|
||||
@doc "The type of the resource, e.g 'post'"
|
||||
@callback type() :: String.t()
|
||||
@doc "A list of attribute names that make up the primary key, e.g [:class, :group]"
|
||||
@callback primary_key() :: [atom]
|
||||
@doc "A list of relationships to other resources"
|
||||
@callback relationships() :: [Ash.relationship()]
|
||||
@doc "A list of actions available for the resource"
|
||||
@callback actions() :: [Ash.action()]
|
||||
@doc "A list of attributes on the resource"
|
||||
@callback attributes() :: [Ash.attribute()]
|
||||
@doc "A list of extensions implemented by the resource"
|
||||
@callback extensions() :: [module]
|
||||
@doc "The data_layer in use by the resource, or nil if there is not one"
|
||||
@callback data_layer() :: module | nil
|
||||
@doc "A description of the resource, to be showed in generated documentation"
|
||||
@callback describe() :: String.t()
|
||||
@doc "A list of authorizers to be used when accessing the resource"
|
||||
@callback authorizers() :: [module]
|
||||
|
||||
defmacro __using__(opts) do
|
||||
quote do
|
||||
|
|
|
@ -49,12 +49,13 @@ defmodule Ash.Type do
|
|||
|
||||
@spec supports_filter?(Ash.resource(), t(), Ash.DataLayer.filter_type(), Ash.data_layer()) ::
|
||||
boolean
|
||||
def supports_filter?(resource, type, filter_type, data_layer) when type in @builtin_names do
|
||||
data_layer.can?(resource, {:filter, filter_type}) and filter_type in @builtins[type][:filters]
|
||||
def supports_filter?(resource, type, filter_type, _data_layer) when type in @builtin_names do
|
||||
Ash.data_layer_can?(resource, {:filter, filter_type}) and
|
||||
filter_type in @builtins[type][:filters]
|
||||
end
|
||||
|
||||
def supports_filter?(resource, type, filter_type, data_layer) do
|
||||
data_layer.can?(resource, {:filter, filter_type}) and
|
||||
Ash.data_layer_can?(resource, {:filter, filter_type}) and
|
||||
filter_type in type.supported_filter_types(data_layer)
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue