Packages
ash
3.5.41
3.29.3
3.29.2
3.29.1
3.29.0
3.28.0
3.27.8
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.0
3.25.2
3.25.1
3.25.0
retired
3.24.7
3.24.6
3.24.5
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.1
3.23.0
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.0
3.19.3
3.19.2
3.19.1
3.19.0
3.18.0
3.17.1
3.17.0
3.16.0
3.15.0
3.14.1
3.14.0
retired
3.13.2
3.13.1
3.13.0
3.12.0
3.11.3
3.11.2
3.11.1
3.11.0
3.10.1
3.10.0
3.9.0
3.8.0
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
retired
3.6.3
retired
3.6.2
3.6.1
3.6.0
3.5.43
3.5.42
3.5.41
3.5.40
3.5.39
3.5.38
3.5.37
3.5.36
3.5.35
3.5.34
3.5.33
3.5.32
3.5.31
3.5.30
3.5.29
3.5.28
3.5.27
3.5.26
3.5.25
3.5.24
3.5.23
3.5.22
3.5.21
3.5.20
3.5.19
3.5.18
3.5.17
3.5.16
3.5.15
3.5.14
3.5.13
3.5.12
3.5.11
3.5.10
3.5.9
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.4.74
retired
3.4.73
3.4.72
3.4.71
3.4.70
3.4.69
3.4.68
3.4.67
3.4.66
3.4.65
3.4.64
3.4.63
3.4.62
3.4.61
3.4.60
3.4.59
3.4.58
3.4.57
3.4.56
3.4.55
3.4.54
3.4.53
3.4.52
3.4.51
3.4.50
3.4.49
3.4.48
3.4.47
3.4.46
3.4.45
3.4.44
3.4.43
3.4.42
3.4.41
3.4.40
3.4.39
3.4.38
3.4.37
3.4.36
3.4.35
3.4.34
3.4.33
3.4.32
3.4.31
3.4.30
3.4.29
3.4.28
3.4.27
3.4.26
3.4.25
3.4.24
3.4.23
3.4.22
3.4.21
3.4.20
3.4.19
3.4.18
3.4.17
3.4.16
3.4.15
3.4.14
3.4.13
3.4.12
3.4.11
3.4.10
3.4.9
3.4.8
3.4.7
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.3
3.3.2
3.3.1
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.16
3.0.15
3.0.14
3.0.13
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.46
3.0.0-rc.45
3.0.0-rc.44
3.0.0-rc.43
3.0.0-rc.42
3.0.0-rc.41
3.0.0-rc.40
3.0.0-rc.39
3.0.0-rc.38
3.0.0-rc.37
3.0.0-rc.36
3.0.0-rc.35
3.0.0-rc.34
3.0.0-rc.33
3.0.0-rc.32
3.0.0-rc.31
3.0.0-rc.29
3.0.0-rc.28
3.0.0-rc.27
3.0.0-rc.26
3.0.0-rc.25
3.0.0-rc.24
3.0.0-rc.23
3.0.0-rc.22
3.0.0-rc.21
3.0.0-rc.20
3.0.0-rc.19
3.0.0-rc.18
3.0.0-rc.17
3.0.0-rc.16
3.0.0-rc.15
3.0.0-rc.14
3.0.0-rc.13
3.0.0-rc.12
3.0.0-rc.11
3.0.0-rc.10
3.0.0-rc.9
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.1
3.0.0-rc.0
2.21.15
2.21.14
2.21.13
2.21.12
2.21.11
2.21.10
2.21.9
2.21.8
2.21.7
2.21.6
2.21.5
2.21.4
2.21.3
2.21.2
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.14
2.19.13
2.19.12
2.19.11
2.19.10
2.19.9
2.19.8
2.19.7
2.19.6
2.19.5
2.19.4
2.19.3
retired
2.19.2
retired
2.19.1
retired
2.19.0
retired
2.18.2
2.18.1
2.18.0
2.17.24
2.17.23
2.17.22
2.17.21
2.17.20
2.17.19
2.17.18
2.17.17
2.17.16
2.17.15
2.17.14
2.17.13
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.1
2.16.0
2.15.20
2.15.19
2.15.18
2.15.17
2.15.16
2.15.15
2.15.14
2.15.13
2.15.12
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.2
2.15.1
2.15.0
2.14.21
2.14.20
2.14.19
2.14.18
2.14.17
2.14.16
2.14.15
2.14.14
2.14.13
2.14.12
2.14.11
2.14.10
2.14.9
2.14.8
2.14.7
2.14.6
2.14.5
2.14.4
2.14.3
2.14.2
2.14.1
2.14.0
2.13.4
retired
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.11
2.11.10
2.11.9
2.11.8
2.11.7
2.11.6
2.11.5
2.11.4
2.11.3
2.11.2
2.11.1
2.11.0
2.11.0-rc.3
2.11.0-rc.2
2.11.0-rc.1
2.11.0-rc.0
2.10.2
2.10.1
2.10.0
2.9.29
2.9.28
2.9.27
2.9.26
2.9.25
2.9.24
2.9.23
2.9.22
2.9.21
2.9.20
2.9.19
2.9.18
2.9.17
2.9.16
2.9.15
2.9.14
2.9.13
2.9.12
2.9.11
2.9.10
2.9.9
2.9.8
2.9.7
2.9.6
2.9.5
2.9.4
2.9.3
2.9.2
2.9.1
2.9.0
2.8.1
2.8.0
2.7.1
2.7.0
2.6.31
2.6.30
2.6.29
2.6.28
2.6.27
2.6.26
2.6.25
2.6.24
2.6.23
2.6.22
2.6.21
2.6.20
2.6.19
2.6.18
2.6.17
2.6.16
2.6.15
2.6.14
2.6.13
2.6.11
2.6.10
2.6.9
2.6.8
2.6.7
2.6.6
2.6.5
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.16
2.5.15
2.5.14
2.5.13
2.5.12
2.5.11
2.5.10
2.5.9
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.5.0-rc.6
2.5.0-rc.5
2.5.0-rc.4
2.5.0-rc.3
2.5.0-rc.2
2.5.0-rc.1
2.5.0-rc.0
2.4.30
2.4.29
2.4.28
2.4.27
2.4.26
2.4.25
2.4.24
2.4.23
2.4.22
2.4.21
2.4.20
2.4.19
2.4.18
2.4.17
2.4.16
2.4.15
2.4.14
2.4.13
2.4.12
2.4.11
2.4.10
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.15
2.0.0-rc.14
2.0.0-rc.13
2.0.0-rc.12
2.0.0-rc.11
2.0.0-rc.10
2.0.0-rc.9
2.0.0-rc.8
2.0.0-rc.7
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-pre.8
2.0.0-pre.7
2.0.0-pre.6
2.0.0-pre.5
2.0.0-pre.4
2.0.0-pre.3
2.0.0-pre.2
2.0.0-pre.1
2.0.0-pre.0
1.53.3
1.53.2
1.53.0
1.52.0-rc.22
1.52.0-rc.21
1.52.0-rc.20
1.52.0-rc.19
1.52.0-rc.18
1.52.0-rc.17
1.52.0-rc.16
1.52.0-rc.15
1.52.0-rc.14
1.52.0-rc.13
1.52.0-rc.12
1.52.0-rc.11
1.52.0-rc.10
1.52.0-rc.9
1.52.0-rc.8
1.52.0-rc.7
1.52.0-rc.6
1.52.0-rc.5
1.52.0-rc.4
1.52.0-rc.3
1.52.0-rc.2
1.52.0-rc.1
1.52.0-rc.0
1.51.2
1.51.1
retired
1.51.0
1.50.21
1.50.20
1.50.19
1.50.18
1.50.17
1.50.16
1.50.15
1.50.14
1.50.13
1.50.12
1.50.11
1.50.10
1.50.9
1.50.8
1.50.7
1.50.6
1.50.5
1.50.4
1.50.3
1.50.2
1.50.1
1.50.0
1.49.0
1.48.0-rc.30
1.48.0-rc.29
1.48.0-rc.28
1.48.0-rc.27
1.48.0-rc.26
1.48.0-rc.25
1.48.0-rc.24
1.48.0-rc.23
1.48.0-rc.22
1.48.0-rc.21
1.48.0-rc.20
1.48.0-rc.19
1.48.0-rc.18
1.48.0-rc.17
1.48.0-rc.16
1.48.0-rc.15
1.48.0-rc.14
1.48.0-rc.13
1.48.0-rc.12
1.48.0-rc.11
1.48.0-rc.10
1.48.0-rc.9
1.48.0-rc.8
1.48.0-rc.7
1.48.0-rc.6
1.48.0-rc.5
1.48.0-rc.4
1.48.0-rc.3
1.48.0-rc.2
1.48.0-rc.1
1.48.0-rc.0
1.47.12
1.47.11
1.47.10
1.47.9
1.47.8
1.47.7
1.47.6
1.47.5
1.47.4
1.47.3
1.47.2
1.47.1
1.47.0
1.46.13
1.46.12
1.46.11
1.46.10
1.46.9
1.46.8
1.46.7
1.46.6
1.46.5
1.46.4
1.46.3
1.46.2
1.46.1
1.46.0
1.45.0-rc9
1.45.0-rc8
1.45.0-rc7
1.45.0-rc6
1.45.0-rc5
1.45.0-rc4
1.45.0-rc3
1.45.0-rc20
1.45.0-rc2
1.45.0-rc19
1.45.0-rc18
1.45.0-rc17
1.45.0-rc16
1.45.0-rc15
1.45.0-rc14
1.45.0-rc13
1.45.0-rc12
1.45.0-rc11
1.45.0-rc10
1.45.0-rc1
1.45.0-rc0
1.44.13
1.44.12
1.44.11
1.44.10
1.44.9
1.44.8
1.44.7
1.44.6
1.44.5
1.44.4
1.44.3
1.44.2
1.44.1
1.44.0
1.43.12
1.43.11
1.43.10
1.43.9
1.43.8
1.43.7
1.43.6
1.43.5
1.43.4
1.43.3
1.43.2
1.43.1
1.43.0
1.42.0
1.41.12
1.41.11
1.41.10
1.41.9
1.41.8
1.41.7
1.41.6
1.41.5
1.41.4
1.41.3
1.41.2
1.41.1
1.41.0
1.40.0
1.39.7
1.39.6
1.39.5
1.39.4
1.39.3
1.39.2
1.39.1
1.39.0
1.38.0
1.37.2
1.37.1
1.37.0
1.36.22
1.36.21
1.36.19
1.36.18
1.36.17
1.36.16
1.36.15
1.36.14
1.36.13
1.36.12
1.36.11
1.36.10
1.36.9
1.36.8
1.36.7
1.36.6
1.36.5
1.36.4
1.36.3
1.36.2
1.36.0
1.35.1
1.35.0
1.34.9
1.34.8
1.34.7
1.34.6
1.34.5
1.34.4
1.34.3
1.34.2
1.34.1
1.34.0
1.33.0
1.32.2
1.32.1
1.32.0
1.31.1
1.31.0
1.30.2
1.30.1
1.29.0-rc1
1.29.0-rc0
1.28.1
1.28.0
1.27.1
1.27.0
1.26.13
1.26.12
1.26.11
1.26.10
1.26.9
1.26.8
1.26.7
1.26.6
1.26.5
1.26.4
1.26.2
1.26.1
1.26.0
1.25.8
1.25.7
1.25.6
1.25.5
1.25.4
1.25.3
1.25.2
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
1.23.3
1.23.2
1.23.1
1.23.0
1.22.1
1.22.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.1
1.18.0
1.17.1
1.17.0
1.16.2
1.15.1
1.15.0
1.14.0
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.1
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.1
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.13.1
0.13.0
0.12.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.9
0.1.8
0.1.3
0.1.1
0.1.0
A declarative, extensible framework for building Elixir applications.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash/resource/info.ex
defmodule Ash.Resource.Info do
@moduledoc "Introspection for resources"
alias Spark.Dsl.Extension
@doc """
Returns the statically configured domain for the resource.
"""
def domain(resource) do
Spark.Dsl.Extension.get_persisted(resource, :domain)
end
@doc """
Retrieves a relationship path from the resource related by path, to the provided resource.
"""
def reverse_relationship(resource, path, acc \\ [])
def reverse_relationship(_, [], acc),
do: acc
def reverse_relationship(resource, [name | rest], acc) do
resource
|> relationships()
|> Enum.find(fn relationship ->
relationship.name == name
end)
|> case do
nil ->
nil
relationship ->
relationship.destination
|> relationships()
|> Enum.find(fn candidate ->
reverse_relationship?(relationship, candidate)
end)
|> case do
nil ->
nil
destination_relationship ->
reverse_relationship(relationship.destination, rest, [
destination_relationship.name | acc
])
end
end
end
defp reverse_relationship?(rel, destination_rel) do
rel.source == destination_rel.destination &&
rel.destination == destination_rel.source &&
rel.source_attribute == destination_rel.destination_attribute &&
rel.destination_attribute == destination_rel.source_attribute &&
Map.fetch(rel, :source_attribute_on_join_resource) ==
Map.fetch(destination_rel, :destination_attribute_on_join_resource) &&
Map.fetch(rel, :destination_attribute_on_join_resource) ==
Map.fetch(destination_rel, :source_attribute_on_join_resource) &&
is_nil(destination_rel.context) &&
is_nil(rel.context)
end
@doc """
The list of code interface definitions.
"""
@spec interfaces(Spark.Dsl.t() | Ash.Resource.t()) :: [Ash.Resource.Interface.t()]
def interfaces(resource) do
resource
|> Extension.get_entities([:code_interface])
|> Enum.filter(fn
%Ash.Resource.Interface{} ->
true
_ ->
false
end)
end
@doc "Get an interface by name from the resource"
@spec interface(Spark.Dsl.t() | Ash.Resource.t(), atom) :: Ash.Resource.Interface.t() | nil
def interface(resource, name) do
resource
|> interfaces()
|> Enum.find(&(&1.name == name))
end
@doc """
The list of code interface calculation definitions.
"""
@spec calculation_interfaces(Spark.Dsl.t() | Ash.Resource.t()) :: [
Ash.Resource.CalculationInterface.t()
]
def calculation_interfaces(resource) do
resource
|> Extension.get_entities([:code_interface])
|> Enum.filter(fn
%Ash.Resource.CalculationInterface{} ->
true
_ ->
false
end)
end
@doc "Get an calculation interface by name from the resource"
@spec calculation_interface(Spark.Dsl.t() | Ash.Resource.t(), atom) ::
Ash.Resource.CalculationInterface.t() | nil
def calculation_interface(resource, name) do
resource
|> calculation_interfaces()
|> Enum.find(&(&1.name == name))
end
@doc """
The domain to define the interface for, when defining it in the resource
"""
@spec code_interface_domain(Spark.Dsl.t() | Ash.Resource.t()) :: atom | nil
def code_interface_domain(resource) do
Extension.get_opt(resource, [:code_interface], :domain, nil)
end
@doc """
Whether or not to define the interface on the resource
"""
@spec define_interface?(Spark.Dsl.t() | Ash.Resource.t()) :: boolean
def define_interface?(resource) do
Extension.get_opt(resource, [:code_interface], :define?, true)
end
@doc """
Whether or not the resource is an embedded resource
"""
@spec embedded?(Spark.Dsl.t() | Ash.Resource.t()) :: boolean
def embedded?(resource) do
Extension.get_persisted(resource, :embedded?, false)
end
@doc """
The description of the resource
"""
@spec description(Spark.Dsl.t() | Ash.Resource.t()) :: String.t() | nil
def description(resource) do
Extension.get_opt(resource, [:resource], :description, nil)
end
@doc """
A list of simple notifiers (require no DSL, used to avoid compile time dependencies)
"""
@spec simple_notifiers(Spark.Dsl.t() | Ash.Resource.t()) :: list(module)
def simple_notifiers(resource) do
Extension.get_persisted(resource, :simple_notifiers, [])
end
@doc """
The trace_name of the resource
"""
@spec trace_name(Spark.Dsl.t() | Ash.Resource.t()) :: String.t() | nil
def trace_name(resource) do
Extension.get_opt(resource, [:resource], :trace_name, nil) || to_string(short_name(resource))
end
@doc """
The short_name of the resource
"""
# sobelow_skip ["DOS.StringToAtom"]
@spec short_name(Spark.Dsl.t() | Ash.Resource.t()) :: atom | nil
def short_name(resource) when is_map(resource) do
Extension.get_opt(resource, [:resource], :short_name, nil) ||
resource
|> Extension.get_persisted(:module)
|> Module.split()
|> List.last()
|> Macro.underscore()
|> String.to_atom()
end
def short_name(resource) do
Extension.get_opt(resource, [:resource], :short_name, nil) || resource.default_short_name()
end
@doc """
Whether to include private fields in the inspect output.
`show_inspect_fields` takes precedence over this option.
"""
@spec inspect_private_fields?(Spark.Dsl.t() | Ash.Resource.t()) :: boolean
def inspect_private_fields?(resource) do
Extension.get_opt(resource, [:resource], :inspect_private_fields?, true)
end
@doc """
A deny-list of fields to hide from the inspect output.
Takes precedence over `show_inspect_fields`.
"""
@spec hide_inspect_fields(Spark.Dsl.t() | Ash.Resource.t()) :: list(atom)
def hide_inspect_fields(resource) do
Extension.get_opt(resource, [:resource], :hide_inspect_fields, [])
end
@doc """
An allow-list of fields to show in the inspect output.
"""
@spec show_inspect_fields(Spark.Dsl.t() | Ash.Resource.t()) :: list(atom) | nil
def show_inspect_fields(resource) do
Extension.get_opt(resource, [:resource], :show_inspect_fields, nil)
end
@doc """
The plural_name of the resource
"""
def plural_name(resource) do
Extension.get_opt(resource, [:resource], :plural_name, nil)
end
@doc """
The base filter of the resource
"""
@spec base_filter(Spark.Dsl.t() | Ash.Resource.t()) :: term
def base_filter(resource) do
Extension.get_opt(resource, [:resource], :base_filter, nil)
end
@doc """
The default context of the resource
"""
@spec default_context(Spark.Dsl.t() | Ash.Resource.t()) :: term
def default_context(resource) do
Extension.get_opt(resource, [:resource], :default_context, nil)
end
@doc "A list of identities for the resource"
@spec identities(Spark.Dsl.t() | Ash.Resource.t()) :: [Ash.Resource.Identity.t()]
def identities(resource) do
Extension.get_entities(resource, [:identities])
end
@doc "Get an identity by name from the resource"
@spec identity(Spark.Dsl.t() | Ash.Resource.t(), atom) :: Ash.Resource.Identity.t() | nil
def identity(resource, name) do
resource
|> identities()
|> Enum.find(&(&1.name == name))
end
@doc "A list of authorizers to be used when accessing"
@spec authorizers(Spark.Dsl.t() | Ash.Resource.t()) :: [module]
def authorizers(resource) do
Extension.get_persisted(resource, :authorizers, [])
end
@doc "A list of notifiers to be used when accessing"
@spec notifiers(Spark.Dsl.t() | Ash.Resource.t()) :: [module]
def notifiers(resource) do
Extension.get_persisted(resource, :notifiers, []) ++ simple_notifiers(resource)
end
@doc "A list of all validations for the resource for a given action type"
@spec validations(Spark.Dsl.t() | Ash.Resource.t(), :create | :update | :destroy | :read) :: [
Ash.Resource.Validation.t()
]
def validations(resource, type) do
Extension.get_persisted(resource, :validations_by_on)[type] ||
resource
|> validations()
|> Enum.filter(&(type in &1.on))
end
@doc "A list of all validations for the resource"
@spec validations(Spark.Dsl.t() | Ash.Resource.t()) :: [Ash.Resource.Validation.t()]
def validations(resource) do
Extension.get_entities(resource, [:validations])
end
@doc "A list of all changes for the resource for a given action type"
@spec changes(Spark.Dsl.t() | Ash.Resource.t(), :create | :update | :destroy) ::
list(
Ash.Resource.Validation.t()
| Ash.Resource.Change.t()
)
def changes(resource, type) do
Extension.get_persisted(resource, :changes_by_on)[type] ||
resource
|> changes()
|> Enum.filter(&(type in &1.on))
end
@doc "A list of all changes for the resource"
@spec changes(Spark.Dsl.t() | Ash.Resource.t()) ::
list(Ash.Resource.Validation.t() | Ash.Resource.Change.t())
def changes(resource) do
Extension.get_entities(resource, [:changes])
end
@spec preparations(Spark.Dsl.t() | Ash.Resource.t(), action_type :: :read | :action) ::
list(Ash.Resource.Preparation.t())
def preparations(resource, type \\ :read) do
Extension.get_entities(resource, [:preparations])
|> Enum.filter(&(type in &1.on))
end
@doc "Whether or not a given module is a resource module"
@spec resource?(module) :: boolean
def resource?(module) when is_atom(module) do
Spark.Dsl.is?(module, Ash.Resource)
end
def resource?(_), do: false
@doc "A list of field names corresponding to the primary key"
@spec primary_key(Spark.Dsl.t() | Ash.Resource.t()) :: list(atom)
def primary_key(resource) do
Spark.Dsl.Extension.get_persisted(resource, :primary_key, [])
end
@doc "A list of unique keys and information for a resource"
@spec unique_keys(Spark.Dsl.t() | Ash.Resource.t()) ::
list(%{type: atom, keys: list(atom), nils_distinct?: boolean()})
def unique_keys(resource) do
Spark.Dsl.Extension.get_persisted(resource, :unique_keys, [])
end
@doc "Whether or not all primary key attributes can be compared with simple_equality"
@spec primary_key_simple_equality?(Spark.Dsl.t() | Ash.Resource.t()) :: boolean()
def primary_key_simple_equality?(resource) do
Spark.Dsl.Extension.get_persisted(resource, :primary_key_simple_equality?, [])
end
@doc "Returns all relationships of a resource"
@spec relationships(Spark.Dsl.t() | Ash.Resource.t()) ::
list(Ash.Resource.Relationships.relationship())
def relationships(resource) do
Extension.get_entities(resource, [:relationships])
end
@doc "Get a relationship by name or path"
@spec relationship(Spark.Dsl.t() | Ash.Resource.t(), atom | String.t() | [atom | String.t()]) ::
Ash.Resource.Relationships.relationship() | nil
def relationship(resource, [name]) do
relationship(resource, name)
end
def relationship(resource, [name | rest]) do
case relationship(resource, name) do
nil ->
nil
relationship ->
relationship(relationship.destination, rest)
end
end
def relationship(resource, relationship_name)
when is_binary(relationship_name) or is_atom(relationship_name) do
case Extension.get_persisted(resource, :relationships_by_name) do
value when is_map(value) ->
value[relationship_name]
nil ->
resource
|> relationships()
|> Enum.find(&(to_string(&1.name) == to_string(relationship_name)))
end
end
def relationship(_, _), do: nil
@doc "Returns all public relationships of a resource"
@spec public_relationships(Spark.Dsl.t() | Ash.Resource.t()) ::
list(Ash.Resource.Relationships.relationship())
def public_relationships(resource) do
resource
|> relationships()
|> Enum.filter(& &1.public?)
end
@doc "The required belongs_to relationships"
def required_belongs_to_relationships(resource) do
Extension.get_persisted(resource, :required_belongs_to_relationships) ||
Enum.filter(
relationships(resource),
&(&1.type == :belongs_to && !&1.allow_nil?)
)
end
def attributes_to_require(resource) do
Extension.get_persisted(resource, :attributes_to_require)
end
def attributes_to_require(resource, action_name) do
Extension.get_persisted(resource, {:attributes_to_require, action_name})
end
@doc "Get a public relationship by name or path"
def public_relationship(resource, [name]) do
public_relationship(resource, name)
end
def public_relationship(resource, [name | rest]) do
case public_relationship(resource, name) do
nil ->
nil
relationship ->
public_relationship(relationship.destination, rest)
end
end
def public_relationship(resource, relationship_name) do
case relationship(resource, relationship_name) do
%{public?: true} = relationship -> relationship
_ -> nil
end
end
@doc "The multitenancy strategy for a resource"
@spec multitenancy_strategy(Spark.Dsl.t() | Ash.Resource.t()) :: :context | :attribute | nil
def multitenancy_strategy(resource) do
Spark.Dsl.Extension.get_opt(resource, [:multitenancy], :strategy, nil)
end
@doc "The multitenancy attribute for a resource"
@spec multitenancy_attribute(Spark.Dsl.t() | Ash.Resource.t()) :: atom | nil
def multitenancy_attribute(resource) do
Spark.Dsl.Extension.get_opt(resource, [:multitenancy], :attribute, nil)
end
@doc "The function to parse the tenant from the attribute"
@spec multitenancy_parse_attribute(Spark.Dsl.t() | Ash.Resource.t()) :: {atom, atom, list(any)}
def multitenancy_parse_attribute(resource) do
Spark.Dsl.Extension.get_opt(
resource,
[:multitenancy],
:parse_attribute,
{__MODULE__, :_identity, []}
)
end
@doc false
def _identity(x), do: x
@doc "The MFA to parse the tenant from the attribute"
@spec multitenancy_global?(Spark.Dsl.t() | Ash.Resource.t()) :: atom | nil
def multitenancy_global?(resource) do
Spark.Dsl.Extension.get_opt(resource, [:multitenancy], :global?, nil)
end
@doc "The template for creating the tenant name"
@spec multitenancy_template(Spark.Dsl.t() | Ash.Resource.t()) :: atom | nil
def multitenancy_template(resource) do
Spark.Dsl.Extension.get_opt(resource, [:multitenancy], :template, nil)
end
@doc "Returns all calculations of a resource"
@spec calculations(Spark.Dsl.t() | Ash.Resource.t()) :: list(Ash.Resource.Calculation.t())
def calculations(resource) do
Extension.get_entities(resource, [:calculations])
end
@doc "Get a calculation by name"
@spec calculation(Spark.Dsl.t() | Ash.Resource.t(), atom | String.t()) ::
Ash.Resource.Calculation.t() | nil
def calculation(resource, name) when is_binary(name) or is_atom(name) do
case Extension.get_persisted(resource, :calculations_by_name) do
value when is_map(value) ->
value[name]
nil ->
resource
|> calculations()
|> Enum.find(&(to_string(&1.name) == to_string(name)))
end
end
def calculation(_, _), do: nil
@doc "Returns all public calculations of a resource"
@spec public_calculations(Spark.Dsl.t() | Ash.Resource.t()) ::
list(Ash.Resource.Calculation.t())
def public_calculations(resource) do
resource
|> Extension.get_entities([:calculations])
|> Enum.filter(& &1.public?)
end
@doc "Get a public calculation by name"
@spec public_calculation(Spark.Dsl.t() | Ash.Resource.t(), atom | String.t()) ::
Ash.Resource.Calculation.t() | nil
def public_calculation(resource, name) when is_binary(name) do
resource
|> calculations()
|> Enum.find(&(to_string(&1.name) == name && &1.public?))
end
def public_calculation(resource, name) do
resource
|> calculations()
|> Enum.find(&(&1.name == name && &1.public?))
end
@doc """
Gets the type of an aggregate for a given resource.
"""
@spec aggregate_type(Spark.Dsl.t() | Ash.Resource.t(), Ash.Resource.Aggregate.t() | atom) ::
{:ok, Ash.Type.t()} | {:error, String.t()}
def aggregate_type(resource, aggregate) when is_atom(aggregate) do
aggregate_type(resource, aggregate(resource, aggregate))
end
def aggregate_type(resource, aggregate) do
attribute =
if aggregate.field do
related = Ash.Resource.Info.related(resource, aggregate.relationship_path)
Ash.Resource.Info.attribute(related, aggregate.field)
end
attribute_type =
if attribute do
attribute.type
end
attribute_constraints =
if attribute do
attribute.constraints
end
case Ash.Query.Aggregate.kind_to_type(aggregate.kind, attribute_type, attribute_constraints) do
{:ok, type, _constraints} ->
{:ok, type}
other ->
other
end
end
@doc "Returns all aggregates of a resource"
@spec aggregates(Spark.Dsl.t() | Ash.Resource.t()) :: list(Ash.Resource.Aggregate.t())
def aggregates(resource) do
Extension.get_entities(resource, [:aggregates])
end
@doc "Get an aggregate by name"
@spec aggregate(Spark.Dsl.t() | Ash.Resource.t(), atom | String.t()) ::
Ash.Resource.Aggregate.t() | nil
def aggregate(resource, name) when is_binary(name) do
resource
|> aggregates()
|> Enum.find(&(to_string(&1.name) == name))
end
def aggregate(resource, name) do
resource
|> aggregates()
|> Enum.find(&(&1.name == name))
end
@doc "Returns all public aggregates of a resource"
@spec public_aggregates(Spark.Dsl.t() | Ash.Resource.t()) :: list(Ash.Resource.Aggregate.t())
def public_aggregates(resource) do
resource
|> Extension.get_entities([:aggregates])
|> Enum.filter(& &1.public?)
end
@doc "Get an aggregate by name"
@spec public_aggregate(Spark.Dsl.t() | Ash.Resource.t(), atom | String.t()) ::
Ash.Resource.Aggregate.t() | nil
def public_aggregate(resource, name) when is_binary(name) do
resource
|> aggregates()
|> Enum.find(&(to_string(&1.name) == name && &1.public?))
end
def public_aggregate(resource, name) do
resource
|> aggregates()
|> Enum.find(&(&1.name == name && &1.public?))
end
@doc "Returns the primary action of the given type"
@spec primary_action!(Spark.Dsl.t() | Ash.Resource.t(), Ash.Resource.Actions.action_type()) ::
Ash.Resource.Actions.action() | no_return
def primary_action!(resource, type) do
case primary_action(resource, type) do
nil ->
raise "Required primary #{type} action for #{inspect(Extension.get_persisted(resource, :module))}."
action ->
action
end
end
@doc "Returns the primary action of a given type"
@spec primary_action(Spark.Dsl.t() | Ash.Resource.t(), Ash.Resource.Actions.action_type()) ::
Ash.Resource.Actions.action() | nil
def primary_action(resource, type) do
resource
|> actions()
|> Enum.find(&(&1.type == type && &1.primary?))
end
@doc "Returns the configured default actions"
@spec default_actions(Spark.Dsl.t() | Ash.Resource.t()) ::
list(:create | :read | :update | :destroy)
def default_actions(resource) do
default =
if embedded?(resource) do
[:create, :read, :update, :destroy]
|> Enum.reject(&Ash.Resource.Info.action(resource, &1))
else
[]
end
Extension.get_opt(
resource,
[:actions],
:defaults,
default
)
end
@doc "Returns all actions of a resource"
@spec actions(Spark.Dsl.t() | Ash.Resource.t()) :: [Ash.Resource.Actions.action()]
def actions(resource) do
Extension.get_entities(resource, [:actions])
end
@doc "Returns the action with the matching name and type on the resource"
@spec action(Spark.Dsl.t() | Ash.Resource.t(), atom(), Ash.Resource.Actions.action_type() | nil) ::
Ash.Resource.Actions.action() | nil
def action(resource, name, type \\ nil) do
# We used to need type, but we don't anymore since action names are unique
if type do
resource
|> actions()
|> Enum.find(&(&1.name == name))
|> case do
nil ->
nil
%{type: ^type} = action ->
action
%{type: found_type} ->
raise ArgumentError, """
Found an action of type #{found_type} while looking for an action of type #{type}
Perhaps you've passed a changeset with the incorrect action type?
"""
end
else
resource
|> actions()
|> Enum.find(&(&1.name == name))
end
end
@doc "Returns true or false if the input is accepted by the action, as an argument or an attribute"
@spec action_input?(Ash.Resource.t(), action :: atom(), input :: atom() | String.t()) ::
boolean()
def action_input?(resource, action, input) do
# Extension.get_persisted(resource, {:action_inputs, action, input}) || false
case Extension.get_persisted(resource, {:action_inputs, action}) do
nil -> false
map_set -> input in map_set
end
end
@doc "Returns the list of possible accepted keys by an action"
@spec action_inputs(Ash.Resource.t(), action :: atom()) ::
MapSet.t()
def action_inputs(resource, action) do
Extension.get_persisted(resource, {:action_inputs, action}) || MapSet.new()
end
@doc "Returns the list of attributes that must be selected for an action invocation"
@spec action_select(Ash.Resource.t(), action :: atom() | Ash.Resource.Actions.action()) ::
list(atom) | nil
def action_select(resource, %{name: name}) do
action_select(resource, name)
end
def action_select(resource, action) do
Extension.get_persisted(resource, {:action_select, action})
end
@doc "Returns all attributes of a resource"
@spec attributes(Spark.Dsl.t() | Ash.Resource.t()) :: [Ash.Resource.Attribute.t()]
def attributes(resource) do
Extension.get_entities(resource, [:attributes])
end
@spec attribute_names(Spark.Dsl.t() | Ash.Resource.t()) :: MapSet.t()
def attribute_names(resource) do
Extension.get_persisted(resource, :attribute_names)
end
@doc "Returns all attributes of a resource with lazy non-matching-defaults"
@spec lazy_non_matching_default_attributes(
Spark.Dsl.t() | Ash.Resource.t(),
type :: :create | :update
) :: [Ash.Resource.Attribute.t()]
def lazy_non_matching_default_attributes(resource, :create) do
Extension.get_persisted(resource, :create_attributes_with_non_matching_lazy_defaults) ||
Enum.filter(attributes(resource), fn attribute ->
!attribute.match_other_defaults? &&
(is_function(attribute.default) or match?({_, _, _}, attribute.default))
end)
end
def lazy_non_matching_default_attributes(resource, :update) do
Extension.get_persisted(resource, :update_attributes_with_non_matching_lazy_defaults) ||
Enum.filter(attributes(resource), fn attribute ->
!attribute.match_other_defaults? &&
(is_function(attribute.update_default) or match?({_, _, _}, attribute.update_default))
end)
end
@doc "Returns all attributes of a resource with static defaults"
@spec static_default_attributes(
Spark.Dsl.t() | Ash.Resource.t(),
type :: :create | :update
) :: [Ash.Resource.Attribute.t()]
def static_default_attributes(resource, :create) do
Extension.get_persisted(resource, :create_attributes_with_static_defaults) ||
resource
|> attributes()
|> Enum.filter(fn attribute ->
not is_nil(attribute.default) &&
not (is_function(attribute.default) or
match?({_, _, _}, attribute.default))
end)
end
def static_default_attributes(resource, :update) do
Extension.get_persisted(resource, :update_attributes_with_static_defaults) ||
resource
|> attributes()
|> Enum.filter(fn attribute ->
not is_nil(attribute.update_default) &&
not (is_function(attribute.update_default) or
match?({_, _, _}, attribute.update_default))
end)
end
@doc "Returns all attributes of a resource with lazy matching defaults"
@spec lazy_matching_default_attributes(
Spark.Dsl.t() | Ash.Resource.t(),
type :: :create | :update
) :: [Ash.Resource.Attribute.t()]
def lazy_matching_default_attributes(resource, :create) do
Extension.get_persisted(resource, :create_attributes_with_matching_defaults) ||
Enum.filter(attributes(resource), fn attribute ->
attribute.match_other_defaults? &&
(is_function(attribute.default) or match?({_, _, _}, attribute.default))
end)
end
def lazy_matching_default_attributes(resource, :update) do
Extension.get_persisted(resource, :update_attributes_with_matching_defaults) ||
resource
|> attributes()
|> Enum.filter(fn attribute ->
not is_nil(attribute.update_default) &&
not (is_function(attribute.update_default) or
match?({_, _, _}, attribute.update_default))
end)
end
@doc "Get an attribute name from the resource"
@spec attribute(Spark.Dsl.t() | Ash.Resource.t(), String.t() | atom) ::
Ash.Resource.Attribute.t() | nil
def attribute(resource, name) when is_binary(name) or is_atom(name) do
case Extension.get_persisted(resource, :attributes_by_name) do
value when is_map(value) ->
value[name]
nil ->
resource
|> attributes()
|> Enum.find(&(to_string(&1.name) == to_string(name)))
end
end
@doc "Returns all public attributes of a resource"
@spec public_attributes(Spark.Dsl.t() | Ash.Resource.t()) :: [Ash.Resource.Attribute.t()]
def public_attributes(resource) do
resource
|> attributes()
|> Enum.filter(& &1.public?)
end
@doc "Get a public attribute name from the resource"
@spec public_attribute(Spark.Dsl.t() | Ash.Resource.t(), String.t() | atom) ::
Ash.Resource.Attribute.t() | nil
def public_attribute(resource, name) do
case attribute(resource, name) do
%{public?: true} = attr -> attr
_ -> nil
end
end
@spec related(Spark.Dsl.t() | Ash.Resource.t(), atom() | String.t() | [atom() | String.t()]) ::
Ash.Resource.t() | nil
def related(resource, relationship) when not is_list(relationship) do
related(resource, [relationship])
end
def related(resource, []), do: resource
def related(resource, [path | rest]) do
case relationship(resource, path) do
%{destination: destination} -> related(destination, rest)
nil -> nil
end
end
@spec always_selected_attribute_names(Spark.Dsl.t() | Ash.Resource.t()) :: MapSet.t()
def always_selected_attribute_names(resource) do
Extension.get_persisted(resource, :always_selected_attribute_names)
end
@spec selected_by_default_attribute_names(Spark.Dsl.t() | Ash.Resource.t()) :: MapSet.t()
def selected_by_default_attribute_names(resource) do
Extension.get_persisted(resource, :selected_by_default_attribute_names)
end
@doc "Returns all attributes, aggregates, calculations and relationships of a resource"
@spec fields(
Spark.Dsl.t() | Ash.Resource.t(),
types :: list(:attributes | :aggregates | :calculations | :relationships)
) :: [
Ash.Resource.Attribute.t()
| Ash.Resource.Aggregate.t()
| Ash.Resource.Calculation.t()
| Ash.Resource.Relationships.relationship()
]
def fields(resource, types \\ [:attributes, :aggregates, :calculations, :relationships]) do
Enum.flat_map(types, &Extension.get_entities(resource, [&1]))
end
@doc "Get a field from a resource by name"
@spec field(Spark.Dsl.t() | Ash.Resource.t(), String.t() | atom) ::
Ash.Resource.Attribute.t()
| Ash.Resource.Aggregate.t()
| Ash.Resource.Calculation.t()
| Ash.Resource.Relationships.relationship()
| nil
def field(resource, name),
do:
attribute(resource, name) ||
aggregate(resource, name) ||
calculation(resource, name) ||
relationship(resource, name)
@doc "Returns all public attributes, aggregates, calculations and relationships of a resource"
@spec public_fields(Spark.Dsl.t() | Ash.Resource.t()) :: [
Ash.Resource.Attribute.t()
| Ash.Resource.Aggregate.t()
| Ash.Resource.Calculation.t()
| Ash.Resource.Relationships.relationship()
]
def public_fields(resource) do
resource
|> fields()
|> Enum.filter(& &1.public?)
end
@doc "Get a public field from a resource by name"
@spec public_field(Spark.Dsl.t() | Ash.Resource.t(), String.t() | atom) ::
Ash.Resource.Attribute.t()
| Ash.Resource.Aggregate.t()
| Ash.Resource.Calculation.t()
| Ash.Resource.Relationships.relationship()
| nil
def public_field(resource, name),
do:
public_attribute(resource, name) ||
public_aggregate(resource, name) ||
public_calculation(resource, name) ||
public_relationship(resource, name)
@doc "Determine if a field is sortable by name"
@spec sortable?(Spark.Dsl.t() | Ash.Resource.t(), String.t() | atom,
pagination_type: Ash.Page.type(),
include_private?: boolean()
) ::
boolean()
def sortable?(resource, name, opts \\ []) do
pagination_type = Keyword.get(opts, :pagination_type, :offset)
include_private? = Keyword.get(opts, :include_private?, true)
field = if include_private?, do: field(resource, name), else: public_field(resource, name)
case field do
nil ->
false
%{type: {:array, _}} ->
false
%{type: Ash.Type.Map} ->
false
%Ash.Resource.Relationships.BelongsTo{} ->
false
%Ash.Resource.Relationships.HasOne{} ->
false
%Ash.Resource.Relationships.HasMany{} ->
false
%Ash.Resource.Relationships.ManyToMany{} ->
false
%Ash.Resource.Calculation{calculation: {module, _}} ->
Code.ensure_compiled(module)
:erlang.function_exported(module, :expression, 2) && pagination_type == :offset
%Ash.Resource.Calculation{} ->
false
%Ash.Resource.Aggregate{kind: :first, relationship_path: relationship_path} = aggregate ->
related = related(resource, relationship_path)
sortable?(related, aggregate.field) && pagination_type == :offset
%Ash.Resource.Aggregate{} ->
pagination_type == :offset
_ ->
true
end
end
@doc "The data layer of the resource, or nil if it does not have one"
@spec data_layer(Ash.Resource.t()) :: Ash.DataLayer.t() | nil
defdelegate data_layer(resource), to: Ash.DataLayer
@doc """
Returns a list of extensions in use by the resource.
"""
@spec extensions(resource :: Ash.Resource.t()) :: list(module())
defdelegate extensions(resource), to: Spark
end