Packages

ash

3.27.5
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
ash lib ash resource schema.ex
Raw

lib/ash/resource/schema.ex

# SPDX-FileCopyrightText: 2019 ash contributors <https://github.com/ash-project/ash/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule Ash.Schema do
@moduledoc false
# Defines an ecto schema for a resource.
# This defines struct representation of a resource. Data layers can rely on this
# schema for persistence.
@old_ecto_attr (case :application.get_key(:ecto, :vsn) do
{:ok, version} ->
!Version.match?(List.to_string(version), ">= 3.13.0")
_ ->
false
end)
# 4.0 *all attributes* need to default to `%Ash.NotLoaded{}`
# that way if you do `%Post{id: id}` you get `%Post{id: id, title: %Ash.NotLoaded{}, body: %Ash.NotLoaded{}}`
# for example
defmacro define_schema do
if Ash.Resource.Info.embedded?(__CALLER__.module) do
quote unquote: false, bind_quoted: [old_ecto_attr?: @old_ecto_attr] do
if Ash.Schema.define?(__MODULE__) do
alias Ash.Query.Aggregate
use Ecto.Schema
@primary_key false
if old_ecto_attr? do
@ecto_derive_inspect_for_redacted_fields false
else
@derive_inspect_for_redacted_fields false
end
embedded_schema do
for attribute <- Ash.Resource.Info.attributes(__MODULE__),
attribute.name not in Ash.Resource.reserved_names() do
read_after_writes? = attribute.generated?
constraint_opts =
case attribute.type do
{:array, _type} ->
attribute.constraints[:items] || []
_ ->
attribute.constraints
end
field(
attribute.name,
Ash.Type.ecto_type(Ash.Schema.not_a_resource!(attribute.type)),
Keyword.merge(constraint_opts,
primary_key: attribute.primary_key?,
read_after_writes: read_after_writes?,
redact: attribute.sensitive?,
source: attribute.source,
default: Ash.Schema.attribute_default(attribute.default)
)
)
end
Ash.Schema.register_ecto_autogenerate(__MODULE__)
field(:aggregates, :map, virtual: true, default: %{})
field(:calculations, :map, virtual: true, default: %{})
field(:__metadata__, :map, virtual: true, default: %{}, redact: true)
field(:__order__, :integer, virtual: true, redact: true)
field(:__lateral_join_source__, :integer, virtual: true, redact: true)
struct_fields_name =
if Module.get_attribute(__MODULE__, :struct_fields) do
:struct_fields
else
:ecto_struct_fields
end
Module.register_attribute(__MODULE__, :ash_struct_fields, accumulate: true)
for field <- Module.get_attribute(__MODULE__, struct_fields_name) do
Module.put_attribute(__MODULE__, :ash_struct_fields, field)
end
for relationship <- Ash.Resource.Info.relationships(__MODULE__),
relationship.name not in Ash.Resource.reserved_names() do
Module.put_attribute(
__MODULE__,
:ash_struct_fields,
{relationship.name,
%Ash.NotLoaded{
type: :relationship,
field: relationship.name,
resource: __MODULE__
}}
)
end
for aggregate <- Ash.Resource.Info.aggregates(__MODULE__),
aggregate.name not in Ash.Resource.reserved_names() do
{:ok, type, _} =
if aggregate.kind == :custom do
{:ok, aggregate.type, []}
else
Aggregate.kind_to_type(aggregate.kind, :string, [])
end
field(
aggregate.name,
Ash.Type.ecto_type(Ash.Schema.not_a_resource!(type)),
virtual: true,
redact: aggregate.sensitive?
)
Module.put_attribute(
__MODULE__,
:ash_struct_fields,
{aggregate.name,
%Ash.NotLoaded{type: :aggregate, field: aggregate.name, resource: __MODULE__}}
)
end
for calculation <- Ash.Resource.Info.calculations(__MODULE__),
calculation.name not in Ash.Resource.reserved_names(),
calculation.field? do
{mod, _} = calculation.calculation
constraint_opts =
case calculation.type do
{:array, _type} ->
calculation.constraints[:items] || []
_ ->
calculation.constraints
end
field(
calculation.name,
Ash.Type.ecto_type(Ash.Schema.not_a_resource!(calculation.type)),
Keyword.merge(constraint_opts,
virtual: true,
redact: calculation.sensitive?
)
)
Module.put_attribute(
__MODULE__,
:ash_struct_fields,
{calculation.name,
%Ash.NotLoaded{type: :calculation, field: calculation.name, resource: __MODULE__}}
)
end
struct_fields = Module.get_attribute(__MODULE__, :ash_struct_fields)
Module.delete_attribute(__MODULE__, struct_fields_name)
Module.register_attribute(__MODULE__, struct_fields_name, accumulate: true)
Enum.each(struct_fields, &Module.put_attribute(__MODULE__, struct_fields_name, &1))
end
after_compile =
@after_compile -- [{Ecto.Schema, :__after_compile__}]
Module.delete_attribute(__MODULE__, :after_compile)
Module.register_attribute(__MODULE__, :after_compile, accumulate: true)
for compile_hook <- after_compile do
@after_compile compile_hook
end
end
end
else
quote unquote: false, bind_quoted: [old_ecto_attr?: @old_ecto_attr] do
if Ash.Schema.define?(__MODULE__) do
alias Ash.Query.Aggregate
use Ecto.Schema
@primary_key false
if old_ecto_attr? do
@ecto_derive_inspect_for_redacted_fields false
else
@derive_inspect_for_redacted_fields false
end
schema Ash.DataLayer.source(__MODULE__) do
for attribute <- Ash.Resource.Info.attributes(__MODULE__),
attribute.name not in Ash.Resource.reserved_names() do
read_after_writes? = attribute.generated?
constraint_opts =
case attribute.type do
{:array, _type} ->
attribute.constraints[:items] || []
_ ->
attribute.constraints
end
field(
attribute.name,
Ash.Type.ecto_type(Ash.Schema.not_a_resource!(attribute.type)),
Keyword.merge(constraint_opts,
primary_key: attribute.primary_key?,
read_after_writes: read_after_writes?,
redact: attribute.sensitive?,
source: attribute.source,
default: Ash.Schema.attribute_default(attribute.default)
)
)
end
Ash.Schema.register_ecto_autogenerate(__MODULE__)
field(:aggregates, :map, virtual: true, default: %{})
field(:calculations, :map, virtual: true, default: %{})
field(:__metadata__, :map, virtual: true, default: %{}, redact: true)
field(:__order__, :integer, virtual: true, redact: true)
field(:__lateral_join_source__, :integer, virtual: true, redact: true)
struct_fields_name =
if Module.get_attribute(__MODULE__, :struct_fields) do
:struct_fields
else
:ecto_struct_fields
end
Module.register_attribute(__MODULE__, :ash_struct_fields, accumulate: true)
for field <- Module.get_attribute(__MODULE__, struct_fields_name) do
Module.put_attribute(__MODULE__, :ash_struct_fields, field)
end
for relationship <- Ash.Resource.Info.relationships(__MODULE__),
relationship.name not in Ash.Resource.reserved_names() do
case relationship do
%{no_attributes?: true} ->
:ok
%{type: :belongs_to} ->
belongs_to relationship.name, relationship.destination,
define_field: false,
references: relationship.destination_attribute,
foreign_key: relationship.source_attribute
%{type: :has_many} ->
has_many relationship.name, relationship.destination,
foreign_key: relationship.destination_attribute,
references: relationship.source_attribute
%{type: :has_one} ->
has_one relationship.name, relationship.destination,
foreign_key: relationship.destination_attribute,
references: relationship.source_attribute
%{type: :many_to_many} ->
many_to_many relationship.name, relationship.destination,
join_through: relationship.through,
join_keys: [
{relationship.source_attribute_on_join_resource,
relationship.source_attribute},
{relationship.destination_attribute_on_join_resource,
relationship.destination_attribute}
]
end
Module.put_attribute(
__MODULE__,
:ash_struct_fields,
{relationship.name,
%Ash.NotLoaded{
type: :relationship,
field: relationship.name,
resource: __MODULE__
}}
)
end
for aggregate <- Ash.Resource.Info.aggregates(__MODULE__),
aggregate.name not in Ash.Resource.reserved_names() do
{:ok, type, _} =
if aggregate.kind == :custom do
{:ok, aggregate.type, []}
else
Aggregate.kind_to_type(aggregate.kind, :string, [])
end
field(
aggregate.name,
Ash.Type.ecto_type(Ash.Schema.not_a_resource!(type)),
virtual: true,
redact: aggregate.sensitive?
)
Module.put_attribute(
__MODULE__,
:ash_struct_fields,
{aggregate.name,
%Ash.NotLoaded{type: :aggregate, field: aggregate.name, resource: __MODULE__}}
)
end
for calculation <- Ash.Resource.Info.calculations(__MODULE__),
calculation.name not in Ash.Resource.reserved_names(),
calculation.field? do
{mod, _} = calculation.calculation
constraint_opts =
case calculation.type do
{:array, _type} ->
calculation.constraints[:items] || []
_ ->
calculation.constraints
end
field(
calculation.name,
Ash.Type.ecto_type(Ash.Schema.not_a_resource!(calculation.type)),
Keyword.merge(constraint_opts,
virtual: true,
redact: calculation.sensitive?
)
)
Module.put_attribute(
__MODULE__,
:ash_struct_fields,
{calculation.name,
%Ash.NotLoaded{type: :calculation, field: calculation.name, resource: __MODULE__}}
)
end
struct_fields = Module.get_attribute(__MODULE__, :ash_struct_fields)
Module.delete_attribute(__MODULE__, struct_fields_name)
Module.register_attribute(__MODULE__, struct_fields_name, accumulate: true)
Enum.each(struct_fields, &Module.put_attribute(__MODULE__, struct_fields_name, &1))
end
if !Kernel.Typespec.defines_type?(__MODULE__, {:t, 0}) do
@type t :: %__MODULE__{}
end
after_compile =
@after_compile -- [{Ecto.Schema, :__after_compile__}]
Module.delete_attribute(__MODULE__, :after_compile)
Module.register_attribute(__MODULE__, :after_compile, accumulate: true)
for compile_hook <- after_compile do
@after_compile compile_hook
end
end
end
end
end
def define?(resource) do
has_action?(resource) or !Enum.empty?(Ash.Resource.Info.fields(resource))
end
defp has_action?(resource) do
resource
|> Ash.Resource.Info.actions()
|> Enum.any?(&(&1.type != :action))
end
@doc false
def not_a_resource!({:array, type}) do
{:array, not_a_resource!(type)}
end
def not_a_resource!(module) when is_atom(module) do
type =
if Ash.Type.NewType.new_type?(module) do
Ash.Type.NewType.subtype_of(module)
else
module
end
if Ash.Resource.Info.resource?(type) && !Ash.Resource.Info.embedded?(type) do
raise """
Non-embedded resources can not be used as types.
Got #{inspect(module)}
To use them as a type, instead use the `:struct` type, with the `instance_of` constraint.
For example:
attribute :foo, :struct, constraints: [instance_of: #{inspect(module)}]
Or as an array:
attribute :foo, {:array, :struct}, constraints: [items: [instance_of: #{inspect(module)}]]
"""
else
module
end
end
def not_a_resource!(type), do: type
def attribute_default(fun) when is_function(fun), do: nil
def attribute_default({_mod, _func, _args}), do: nil
def attribute_default(value), do: value
@doc false
# Called at compile time from within the `schema do ... end` block to wire
# Ash's create_timestamp/update_timestamp attributes into Ecto's autogenerate
# system. This enables Repo.insert/1 and Repo.update/1 to auto-populate
# timestamp fields without going through Ash's changeset pipeline.
def register_ecto_autogenerate(module) do
autogen_timestamps =
Ash.Resource.Info.attributes(module)
|> Enum.filter(&timestamp_attribute?/1)
# Partition by match_other_defaults?: timestamps that share defaults are
# grouped into a single {[field1, field2], mfa} tuple so Ecto calls the MFA
# once and assigns the same value to all fields. Timestamps with
# match_other_defaults? == false each get their own entry.
{shared, individual} =
Enum.split_with(autogen_timestamps, & &1.match_other_defaults?)
shared
|> Enum.group_by(&Ash.Type.storage_type(&1.type, &1.constraints))
|> Enum.each(fn {storage_type, attrs} ->
field_names = Enum.map(attrs, & &1.name)
Module.put_attribute(
module,
:ecto_autogenerate,
{field_names, {Ecto.Schema, :__timestamps__, [storage_type]}}
)
end)
Enum.each(individual, fn attr ->
storage_type = Ash.Type.storage_type(attr.type, attr.constraints)
Module.put_attribute(
module,
:ecto_autogenerate,
{[attr.name], {Ecto.Schema, :__timestamps__, [storage_type]}}
)
end)
# Register update_timestamp fields in :ecto_autoupdate so Repo.update/1
# refreshes them automatically.
update_timestamps =
Enum.filter(autogen_timestamps, fn attr -> not is_nil(attr.update_default) end)
{shared_update, individual_update} =
Enum.split_with(update_timestamps, & &1.match_other_defaults?)
shared_update
|> Enum.group_by(&Ash.Type.storage_type(&1.type, &1.constraints))
|> Enum.each(fn {storage_type, attrs} ->
field_names = Enum.map(attrs, & &1.name)
Module.put_attribute(
module,
:ecto_autoupdate,
{field_names, {Ecto.Schema, :__timestamps__, [storage_type]}}
)
end)
Enum.each(individual_update, fn attr ->
storage_type = Ash.Type.storage_type(attr.type, attr.constraints)
Module.put_attribute(
module,
:ecto_autoupdate,
{[attr.name], {Ecto.Schema, :__timestamps__, [storage_type]}}
)
end)
end
@doc false
def timestamp_attribute?(attr) do
ecto_datetime_type?(Ash.Type.storage_type(attr.type, attr.constraints)) &&
attr.writable? == false &&
datetime_default?(attr.default) &&
attr.allow_nil? == false
end
defp datetime_default?(fun) when is_function(fun, 0) do
fun == (&DateTime.utc_now/0)
end
defp datetime_default?(_), do: false
defp ecto_datetime_type?(type) do
type in [:utc_datetime, :utc_datetime_usec, :naive_datetime, :naive_datetime_usec]
end
end