Packages

ash

2.18.1
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 actions helpers.ex
Raw

lib/ash/actions/helpers.ex

defmodule Ash.Actions.Helpers do
@moduledoc false
require Logger
require Ash.Flags
def rollback_if_in_transaction({:error, error}, resource, changeset) do
if Ash.DataLayer.in_transaction?(resource) do
case changeset do
%Ash.Changeset{} = changeset ->
Ash.DataLayer.rollback(resource, Ash.Changeset.add_error(changeset, error))
%Ash.Query{} = query ->
Ash.DataLayer.rollback(resource, Ash.Query.add_error(query, error))
_ ->
Ash.DataLayer.rollback(resource, Ash.Error.to_error_class(error))
end
else
{:error, error}
end
end
def rollback_if_in_transaction({:error, :no_rollback, error}, _, _changeset) do
{:error, error}
end
def rollback_if_in_transaction(success, _, _), do: success
def validate_calculation_load!(%{__struct__: Ash.Query}, module) do
raise """
`#{inspect(module)}.load/3` returned a query.
Returning a query from the `load/3` callback of a calculation is now deprecated.
Instead, return the load statement itself, i.e instead of `Ash.Query.load(query, [...])`,
just return `[...]`. This is so that Ash can examine the requirements of just this single
calculation to ensure that all required values are present
"""
end
def validate_calculation_load!(other, _), do: other
defp set_context(%{__struct__: Ash.Changeset} = changeset, context),
do: Ash.Changeset.set_context(changeset, context)
defp set_context(%{__struct__: Ash.Query} = query, context),
do: Ash.Query.set_context(query, context)
defp set_context(%{__struct__: Ash.ActionInput} = action_input, context),
do: Ash.ActionInput.set_context(action_input, context)
def add_process_context(api, query_or_changeset, opts) do
query_or_changeset = set_context(query_or_changeset, opts[:context] || %{})
api = api || query_or_changeset.api
opts =
case query_or_changeset.context do
%{
private: %{
actor: actor
}
} ->
Keyword.put_new(opts, :actor, actor)
_ ->
opts
end
opts =
case query_or_changeset.context do
%{
private: %{
authorize?: authorize?
}
} ->
Keyword.put_new(opts, :authorize?, authorize?)
_ ->
opts
end
opts =
case query_or_changeset.context do
%{
private: %{
tracer: tracer
}
} ->
do_add_tracer(opts, tracer)
_ ->
opts
end
opts = set_opts(opts, api, query_or_changeset)
query_or_changeset = add_context(query_or_changeset, opts)
{query_or_changeset, opts}
end
def set_opts(opts, api, query_or_changeset \\ nil) do
opts
|> add_actor(query_or_changeset, api)
|> add_authorize?(api)
|> add_tenant()
|> add_tracer()
end
def add_context(query_or_changeset, opts) do
context = Process.get(:ash_context, %{}) || %{}
private_context = Map.new(Keyword.take(opts, [:actor, :authorize?]))
context =
context
|> Map.merge(private_context)
|> Map.put_new(:actor, nil)
|> Map.put_new(:authorize?, false)
case query_or_changeset do
%{__struct__: Ash.ActionInput} ->
query_or_changeset
|> Ash.ActionInput.set_context(context)
|> Ash.ActionInput.set_context(%{private: private_context})
|> Ash.ActionInput.set_tenant(query_or_changeset.tenant || opts[:tenant])
%{__struct__: Ash.Query} ->
query_or_changeset
|> Ash.Query.set_context(context)
|> Ash.Query.set_context(%{private: private_context})
|> Ash.Query.set_tenant(query_or_changeset.tenant || opts[:tenant])
%{__struct__: Ash.Changeset} ->
query_or_changeset
|> Ash.Changeset.set_context(context)
|> Ash.Changeset.set_context(%{
private: private_context
})
|> Ash.Changeset.set_tenant(query_or_changeset.tenant || opts[:tenant])
end
end
defp add_actor(opts, query_or_changeset, api) do
opts =
if Keyword.has_key?(opts, :actor) do
opts
else
case Process.get(:ash_actor) do
{:actor, value} ->
Keyword.put(opts, :actor, value)
_ ->
opts
end
end
if api do
if !internal?(query_or_changeset) && !Keyword.has_key?(opts, :actor) &&
Ash.Api.Info.require_actor?(api) do
raise Ash.Error.to_error_class(Ash.Error.Forbidden.ApiRequiresActor.exception(api: api))
end
opts
else
# The only time api would be nil here is when we call this helper inside of `Changeset.for_*` and `Query.for_read`
# meaning this will be run again later with the api, so we skip the validations on the api
opts
end
end
defp internal?(%{context: %{private: %{internal?: true}}}), do: true
defp internal?(_), do: false
defp add_authorize?(opts, api) do
opts =
if Keyword.has_key?(opts, :authorize?) do
opts
else
case Process.get(:ash_authorize?) do
{:authorize?, value} ->
Keyword.put(opts, :authorize?, value)
_ ->
opts
end
end
if api do
case Ash.Api.Info.authorize(api) do
:always ->
Keyword.put(opts, :authorize?, true)
:by_default ->
Keyword.put_new(opts, :authorize?, true)
:when_requested ->
if Keyword.has_key?(opts, :actor) do
Keyword.put_new(opts, :authorize?, true)
else
Keyword.put(opts, :authorize?, opts[:authorize?] || Keyword.has_key?(opts, :actor))
end
end
else
# The only time api would be nil here is when we call this helper inside of `Changeset.for_*` and `Query.for_read`
# meaning this will be run again later with the api, so we skip the validations on the api
opts
end
end
defp add_tenant(opts) do
if Keyword.has_key?(opts, :tenant) do
opts
else
case Process.get(:ash_tenant) do
{:tenant, value} ->
Keyword.put(opts, :tenant, value)
_ ->
opts
end
end
end
defp add_tracer(opts) do
opts =
case Process.get(:ash_tracer) do
{:tracer, value} ->
do_add_tracer(opts, value)
_ ->
opts
end
case Application.get_env(:ash, :tracer) do
nil ->
opts
tracer ->
do_add_tracer(opts, tracer)
end
end
defp do_add_tracer(opts, tracer) do
tracer = List.wrap(tracer)
Keyword.update(opts, :tracer, tracer, fn existing_tracer ->
if is_list(existing_tracer) do
Enum.uniq(tracer ++ existing_tracer)
else
if is_nil(existing_tracer) do
tracer
else
Enum.uniq(tracer ++ existing_tracer)
end
end
end)
end
@doc false
def notify({:ok, record, instructions}, changeset, opts) do
resource_notification = resource_notification(changeset, record, opts)
if opts[:return_notifications?] do
{:ok, record,
Map.update(
instructions,
:notifications,
[resource_notification],
&[resource_notification | &1]
)}
else
if Process.get(:ash_started_transaction?) do
current_notifications = Process.get(:ash_notifications, [])
Process.put(
:ash_notifications,
[resource_notification | current_notifications]
)
else
unsent_notifications = Ash.Notifier.notify([resource_notification])
warn_missed!(changeset.resource, changeset.action, %{
resource_notifications: unsent_notifications
})
end
{:ok, record, instructions}
end
end
def notify(other, _changeset, _opts), do: other
defp resource_notification(changeset, result, opts) do
%Ash.Notifier.Notification{
resource: changeset.resource,
api: changeset.api,
actor: changeset.context[:private][:actor],
action: changeset.action,
data: result,
changeset: changeset,
from: self(),
metadata: opts[:notification_metadata] || %{}
}
end
def warn_missed!(resource, action, result) do
case Map.get(result, :resource_notifications, Map.get(result, :notifications, [])) do
empty when empty in [nil, []] ->
:ok
missed ->
case Application.get_env(:ash, :missed_notifications, :warn) do
:ignore ->
:ok
:raise ->
raise """
Missed #{Enum.count(missed)} notifications in action #{inspect(resource)}.#{action.name}.
This happens when the resources are in a transaction, and you did not pass
`return_notifications?: true`. If you are in a changeset hook, you can
return the notifications. If not, you can send the notifications using
`Ash.Notifier.notify/1` once your resources are out of a transaction.
To ignore these in all cases:
config :ash, :missed_notifications, :ignore
To turn this into warnings:
config :ash, :missed_notifications, :warn
"""
:warn ->
{:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
Logger.warning("""
Missed #{Enum.count(missed)} notifications in action #{inspect(resource)}.#{action.name}.
This happens when the resources are in a transaction, and you did not pass
`return_notifications?: true`. If you are in a changeset hook, you can
return the notifications. If not, you can send the notifications using
`Ash.Notifier.notify/1` once your resources are out of a transaction.
#{Exception.format_stacktrace(stacktrace)}
While you should likely leave this setting on, you can ignore these or turn them into errors.
To ignore these in all cases:
config :ash, :missed_notifications, :ignore
To turn this into raised errors:
config :ash, :missed_notifications, :raise
""")
end
end
end
def process_errors(changeset, [error]) do
%{changeset | errors: []}
|> Ash.Changeset.add_error(error)
|> Map.get(:errors)
|> case do
[error] ->
error
errors ->
errors
end
end
def process_errors(changeset, errors) when is_list(errors) do
%{changeset | errors: []}
|> Ash.Changeset.add_error(errors)
|> Map.get(:errors)
end
def process_errors(changeset, error), do: process_errors(changeset, [error])
def load_runtime_types({:ok, results}, query, attributes?) do
load_runtime_types(results, query, attributes?)
end
def load_runtime_types({:error, error}, _query, _attributes?) do
{:error, error}
end
def load_runtime_types(results, query, attributes?) when is_list(results) do
attributes = runtime_attributes(query, attributes?)
calcs = runtime_calculations(query)
if Enum.empty?(attributes) && Enum.empty?(calcs) do
{:ok, results}
else
Enum.reduce_while(results, {:ok, []}, fn result, {:ok, results} ->
case do_load_runtime_types(result, attributes, calcs) do
{:ok, result} ->
{:cont, {:ok, [result | results]}}
{:error, error} ->
{:halt, {:error, error}}
end
end)
|> case do
{:ok, results} -> {:ok, Enum.reverse(results)}
{:error, error} -> {:error, error}
end
end
end
def load_runtime_types(nil, _, _attributes?), do: {:ok, nil}
def load_runtime_types(result, query, attributes?) do
do_load_runtime_types(
result,
runtime_attributes(query, attributes?),
runtime_calculations(query)
)
end
defp runtime_attributes(query, true) do
case query.select do
nil ->
Ash.Resource.Info.attributes(query.resource)
select ->
Enum.map(select, &Ash.Resource.Info.attribute(query.resource, &1))
end
|> Enum.reject(fn %{type: type, constraints: constraints} ->
Ash.Type.cast_in_query?(type, constraints)
end)
end
defp runtime_attributes(_, _), do: []
defp runtime_calculations(query) do
query.calculations
|> Kernel.||(%{})
|> Enum.reject(fn {_name, calc} ->
constraints = Map.get(calc, :constraints, [])
if function_exported?(Ash.Type, :cast_in_query?, 2) do
Ash.Type.cast_in_query?(calc.type, constraints)
else
Ash.Type.cast_in_query?(calc.type)
end
end)
end
defp do_load_runtime_types(record, select, calculations) do
select
|> Enum.reduce_while({:ok, record}, fn attr, {:ok, record} ->
case Map.get(record, attr.name) do
nil ->
{:cont, {:ok, record}}
%Ash.NotLoaded{} ->
{:cont, {:ok, record}}
%Ash.ForbiddenField{} ->
{:cont, {:ok, record}}
value ->
case Ash.Type.cast_stored(
attr.type,
value,
attr.constraints
) do
{:ok, value} ->
{:cont, {:ok, Map.put(record, attr.name, value)}}
:error ->
{:halt, {:error, message: "is invalid", field: attr.name}}
end
end
end)
|> case do
{:ok, record} ->
Enum.reduce_while(calculations, {:ok, record}, fn {name, calc}, {:ok, record} ->
case calc.load do
nil ->
case Map.get(record.calculations || %{}, calc.name) do
nil ->
{:cont, {:ok, record}}
value ->
case Ash.Type.cast_stored(
calc.type,
value,
Map.get(calc, :constraints, [])
) do
{:ok, value} ->
{:cont,
{:ok, Map.update!(record, :calculations, &Map.put(&1, name, value))}}
:error ->
{:halt, {:error, message: "is invalid", field: calc.name}}
end
end
load ->
case Map.get(record, load) do
nil ->
{:cont, {:ok, record}}
value ->
case Ash.Type.cast_stored(
calc.type,
value,
Map.get(calc, :constraints, [])
) do
{:ok, casted} ->
{:cont, {:ok, Map.put(record, load, casted)}}
:error ->
{:halt, {:error, message: "is invalid", field: calc.name}}
end
end
end
end)
other ->
other
end
end
def load({:ok, result, instructions}, changeset, api, opts) do
if changeset.load in [nil, []] do
{:ok, result, instructions}
else
query =
changeset.resource
|> Ash.Query.load(changeset.load)
|> select_selected(result)
case api.load(result, query, opts) do
{:ok, result} ->
{:ok, result, instructions}
{:error, error} ->
{:error, error}
end
end
end
def load({:ok, result}, changeset, api, opts) do
if changeset.load in [nil, []] do
{:ok, result, %{}}
else
query =
changeset.resource
|> Ash.Query.load(changeset.load)
|> select_selected(result)
case api.load(result, query, opts) do
{:ok, result} ->
{:ok, result, %{}}
{:error, error} ->
{:error, error}
end
end
end
def load(other, _, _, _), do: other
defp select_selected(query, result) do
select =
query.resource
|> Ash.Resource.Info.attributes()
|> Enum.filter(&Ash.Resource.selected?(result, &1.name))
|> Enum.map(& &1.name)
Ash.Query.ensure_selected(query, select)
end
def restrict_field_access({:ok, record, instructions}, query_or_changeset) do
{:ok, restrict_field_access(record, query_or_changeset), instructions}
end
def restrict_field_access({:ok, record}, query_or_changeset) do
{:ok, restrict_field_access(record, query_or_changeset)}
end
def restrict_field_access({:error, error}, _), do: {:error, error}
def restrict_field_access(records, query_or_changeset) when is_list(records) do
Enum.map(records, &restrict_field_access(&1, query_or_changeset))
end
def restrict_field_access(%_{} = record, query_or_changeset) do
if internal?(query_or_changeset) do
record
else
record.calculations
|> Enum.reduce(record, fn
{{:__ash_fields_are_visible__, fields}, value}, record ->
if value do
record
else
Enum.reduce(fields, record, fn field, record ->
type =
case Ash.Resource.Info.field(query_or_changeset.resource, field) do
%Ash.Resource.Aggregate{} -> :aggregate
%Ash.Resource.Attribute{} -> :attribute
%Ash.Resource.Calculation{} -> :calculation
end
record
|> Map.put(field, %Ash.ForbiddenField{field: field, type: type})
|> replace_dynamic_loads(field, type, query_or_changeset)
end)
end
|> Map.update!(
:calculations,
&Map.delete(&1, {:__ash_fields_are_visible__, fields})
)
_, record ->
record
end)
end
end
defp replace_dynamic_loads(record, _, :aggregate, _), do: record
defp replace_dynamic_loads(record, field, type, %Ash.Changeset{} = changeset)
when type in [:attribute, :calculation] do
query =
changeset.resource
|> Ash.Query.new()
|> Ash.Query.load(changeset.load)
replace_dynamic_loads(record, field, type, query)
end
defp replace_dynamic_loads(record, field, type, query)
when type in [:attribute, :calculation] do
Enum.reduce(
query.calculations,
record,
fn
{key, %{module: Ash.Resource.Calculation.LoadAttribute, opts: opts, load: load}},
record ->
if type == :attribute && opts[:attribute] == field do
if load do
Map.put(record, load, %Ash.ForbiddenField{field: load, type: type})
else
Map.update!(
record,
:calculations,
&Map.put(&1, key, %Ash.ForbiddenField{field: field, type: type})
)
end
else
record
end
{key, %{calc_name: calc_name, load: load}}, record ->
if calc_name == field and type == :calculation do
if load do
Map.put(record, load, %Ash.ForbiddenField{field: load, type: type})
else
Map.update!(
record,
:calculations,
&Map.put(&1, key, %Ash.ForbiddenField{field: field, type: type})
)
end
else
record
end
_, record ->
record
end
)
end
def select({:ok, results, instructions}, query) do
{:ok, select(results, query), instructions}
end
def select({:ok, results}, query) do
{:ok, select(results, query)}
end
def select({:error, error}, _query) do
{:error, error}
end
def select(results, query) when is_list(results) do
Enum.map(results, &select(&1, query))
end
def select(nil, _), do: nil
def select(result, %{select: nil}) do
result
end
def select(result, %{resource: resource, select: select}) do
resource
|> Ash.Resource.Info.attributes()
|> Enum.flat_map(fn attribute ->
if attribute.always_select? || attribute.primary_key? || attribute.name in select do
[]
else
[attribute.name]
end
end)
|> Enum.reduce(result, fn key, record ->
default_field_value =
if Ash.Flags.ash_three?() do
%Ash.NotSelected{field: key}
else
nil
end
Map.put(record, key, default_field_value)
end)
|> Ash.Resource.put_metadata(:selected, select)
end
def attributes_to_select(%{select: nil, resource: resource}) do
resource
|> Ash.Resource.Info.attributes()
|> Enum.map(& &1.name)
end
def attributes_to_select(%{select: select, resource: resource}) do
resource
|> Ash.Resource.Info.attributes()
|> Enum.flat_map(fn attribute ->
if attribute.always_select? || attribute.primary_key? || attribute.name in select do
[attribute.name]
else
[]
end
end)
end
end