docs: update documentation

This commit is contained in:
Zach Daniel 2020-06-04 02:16:41 -04:00
parent bf1fafef3c
commit 797792f947
No known key found for this signature in database
GPG key ID: C377365383138D4B
8 changed files with 87 additions and 64 deletions

View file

@ -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

View file

@ -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 ->

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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))

View file

@ -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

View file

@ -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