Packages

ash

3.16.0
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 create create.ex
Raw

lib/ash/actions/create/create.ex

# SPDX-FileCopyrightText: 2019 ash contributors <https://github.com/ash-project/ash/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule Ash.Actions.Create do
@moduledoc false
alias Ash.Actions.Helpers
require Ash.Tracer
@spec run(Ash.Domain.t(), Ash.Changeset.t(), Ash.Resource.Actions.action(), Keyword.t()) ::
{:ok, Ash.Resource.record(), list(Ash.Notifier.Notification.t())}
| {:ok, Ash.Resource.record()}
| {:error, term}
def run(domain, changeset, action, opts) do
is_upsert? = changeset.context[:private][:upsert?]
create_atomics_unsupported? =
changeset.create_atomics != [] &&
!Ash.DataLayer.data_layer_can?(changeset.resource, {:atomic, :create})
atomics_unsupported? =
changeset.atomics != [] &&
if is_upsert?,
do: !Ash.DataLayer.data_layer_can?(changeset.resource, {:atomic, :upsert}),
else: true
cond do
create_atomics_unsupported? ->
{:error,
Ash.Error.Invalid.AtomicsNotSupported.exception(
resource: changeset.resource,
action_type: :create
)}
atomics_unsupported? and not is_upsert? ->
{:error,
Ash.Error.Changes.InvalidChanges.exception(
message:
"atomic_update cannot be used on create actions. Use atomic_set for setting values during create."
)}
atomics_unsupported? ->
{:error,
Ash.Error.Invalid.AtomicsNotSupported.exception(
resource: changeset.resource,
action_type: :create
)}
true ->
changeset =
if Map.get(action, :multitenancy) in [:bypass, :bypass_all] do
Ash.Changeset.set_context(changeset, %{
shared: %{private: %{multitenancy: :bypass_all}}
})
else
changeset
end
{changeset, opts} = Ash.Actions.Helpers.set_context_and_get_opts(domain, changeset, opts)
changeset = Helpers.apply_opts_load(changeset, opts)
Ash.Tracer.span :action,
fn ->
Ash.Domain.Info.span_name(
domain,
changeset.resource,
action.name
)
end,
opts[:tracer] do
metadata = fn ->
%{
domain: domain,
resource: changeset.resource,
resource_short_name: Ash.Resource.Info.short_name(changeset.resource),
actor: opts[:actor],
tenant: opts[:tenant],
action: action.name,
authorize?: opts[:authorize?]
}
end
Ash.Tracer.set_metadata(opts[:tracer], :action, metadata)
Ash.Tracer.telemetry_span [:ash, Ash.Domain.Info.short_name(domain), :create],
metadata do
case do_run(domain, changeset, action, opts) do
{:error, error} ->
error =
Ash.Error.to_error_class(
error,
bread_crumbs:
"Error returned from: #{inspect(changeset.resource)}.#{action.name}"
)
if opts[:tracer] do
stacktrace =
case error do
%{stacktrace: %{stacktrace: stacktrace}} ->
stacktrace || []
_ ->
{:current_stacktrace, stacktrace} =
Process.info(self(), :current_stacktrace)
stacktrace
end
Ash.Tracer.set_handled_error(opts[:tracer], Ash.Error.to_error_class(error),
stacktrace: stacktrace
)
end
{:error, error}
other ->
other
end
end
end
end
rescue
e ->
reraise Ash.Error.to_error_class(e,
changeset: changeset,
stacktrace: __STACKTRACE__,
bread_crumbs: [
"Exception raised in: #{inspect(changeset.resource)}.#{action.name}"
]
),
__STACKTRACE__
end
defp do_run(domain, changeset, action, opts) do
opts =
opts
|> Keyword.put(
:upsert?,
action.upsert? || opts[:upsert?] || get_in(changeset.context, [:private, :upsert?]) ||
false
)
upsert_identity =
if opts[:upsert?] do
action.upsert_identity || opts[:upsert_identity] ||
get_in(changeset.context, [:private, :upsert_identity])
else
opts[:upsert_identity] || get_in(changeset.context, [:private, :upsert_identity])
end
upsert_fields =
Ash.Changeset.expand_upsert_fields(
opts[:upsert_fields] || get_in(changeset.context, [:private, :upsert_fields]) ||
action.upsert_fields,
changeset.resource
)
opts =
if get_in(changeset.context, [:private, :return_skipped_upsert?]) do
Keyword.put(opts, :return_skipped_upsert?, true)
else
opts
end
opts =
Keyword.put(opts, :upsert_identity, upsert_identity)
changeset =
Ash.Changeset.set_context(changeset, %{
private: %{
upsert?: true,
upsert_identity: upsert_identity,
upsert_fields: upsert_fields
}
})
with %{valid?: true} = changeset <- handle_multitenancy(changeset, action),
%{valid?: true} = changeset <- changeset(changeset, domain, action, opts),
%{valid?: true} = changeset <- check_upsert_support(changeset, opts),
%{valid?: true} = changeset <- authorize(changeset, opts),
{:ok, result, instructions} <- commit(changeset, domain, opts) do
add_notifications(
changeset.resource,
result,
changeset.action,
instructions,
opts[:return_notifications?]
)
end
|> case do
{:ok, result} ->
{:ok, result}
{:ok, result, notifications} ->
{:ok, result, notifications}
:ok ->
:ok
%Ash.Changeset{errors: errors} = changeset ->
errors = Helpers.process_errors(changeset, errors)
Ash.Changeset.run_after_transactions(
{:error, Ash.Error.to_error_class(errors, changeset: changeset)},
changeset
)
{:error, error} ->
errors = Helpers.process_errors(changeset, List.wrap(error))
{:error, Ash.Error.to_error_class(errors, changeset: changeset)}
end
end
defp authorize(changeset, opts) do
if opts[:authorize?] do
case Ash.can(changeset, opts[:actor],
alter_source?: true,
pre_flight?: true,
return_forbidden_error?: true,
maybe_is: false
) do
{:ok, true, changeset} ->
changeset
{:ok, false, error} ->
Ash.Changeset.add_error(changeset, error)
{:error, error} ->
Ash.Changeset.add_error(changeset, error)
end
else
changeset
end
end
defp add_tenant({:ok, nil}, _), do: {:ok, nil}
defp add_tenant({:ok, data}, changeset) do
if changeset.tenant do
{:ok, %{data | __metadata__: Map.put(data.__metadata__, :tenant, changeset.tenant)}}
else
{:ok, data}
end
end
defp add_tenant(other, _), do: other
defp add_notifications(
resource,
result,
action,
instructions,
return_notifications?
) do
if return_notifications? do
{:ok, result, Map.get(instructions, :notifications, [])}
else
Helpers.warn_missed!(resource, action, instructions)
{:ok, result}
end
end
defp changeset(changeset, domain, action, opts) do
changeset = %{changeset | domain: domain}
if changeset.__validated_for_action__ == action.name do
changeset
else
Ash.Changeset.for_create(changeset, action.name, %{}, opts)
end
|> Ash.Changeset.set_defaults(:create, true)
|> Ash.Changeset.timeout(opts[:timeout] || changeset.timeout)
end
defp commit(changeset, domain, opts) do
upsert_keys =
case opts[:upsert_identity] do
nil ->
Ash.Resource.Info.primary_key(changeset.resource)
identity ->
identity =
changeset.resource
|> Ash.Resource.Info.identities()
|> Enum.find(&(&1.name == identity))
|> Kernel.||(
raise Ash.Error.Invalid.NoIdentityFound,
resource: changeset.resource,
identity: identity
)
keys = identity |> Map.get(:keys)
if identity.all_tenants? do
keys
else
if Ash.Resource.Info.multitenancy_strategy(changeset.resource) == :attribute &&
(changeset.tenant || !identity.nils_distinct?) do
[Ash.Resource.Info.multitenancy_attribute(changeset.resource) | keys]
else
keys
end
end
end
changeset = set_tenant(changeset)
is_upsert? = changeset.context[:private][:upsert?]
can_atomic_create? =
Ash.DataLayer.data_layer_can?(changeset.resource, {:atomic, :create})
can_atomic_upsert? =
Ash.DataLayer.data_layer_can?(changeset.resource, {:atomic, :upsert})
result =
changeset
|> Ash.Changeset.with_hooks(
fn
%{create_atomics: create_atomics}
when create_atomics != [] and not can_atomic_create? ->
{:error,
Ash.Error.Invalid.AtomicsNotSupported.exception(
resource: changeset.resource,
action_type: :create
)}
%{atomics: atomics} when atomics != [] and is_upsert? and not can_atomic_upsert? ->
{:error,
Ash.Error.Invalid.AtomicsNotSupported.exception(
resource: changeset.resource,
action_type: :create
)}
changeset ->
changeset =
changeset
|> Ash.Changeset.hydrate_atomic_refs(opts[:actor])
|> Ash.Changeset.apply_atomic_constraints(opts[:actor])
|> Ash.Changeset.set_action_select()
case Ash.Actions.ManagedRelationships.setup_managed_belongs_to_relationships(
changeset,
opts[:actor],
authorize?: opts[:authorize?],
actor: opts[:actor]
) do
{:error, error} ->
{:error, error}
{changeset, manage_instructions} ->
changeset =
if changeset.context[:private][:action_result] do
changeset
else
Ash.Changeset.require_values(
changeset,
:create
)
|> Ash.Changeset.require_values(
:update,
false,
changeset.action.require_attributes
)
end
if changeset.valid? do
if changeset.action.manual do
{mod, action_opts} = changeset.action.manual
if result = changeset.context[:private][:action_result] do
result
else
mod.create(changeset, action_opts, %Ash.Resource.ManualCreate.Context{
select: opts[:select],
actor: opts[:actor],
source_context: changeset.context,
tenant: changeset.tenant,
tracer: opts[:tracer],
authorize?: opts[:authorize?],
domain: changeset.domain,
upsert?: opts[:upsert?],
upsert_keys: upsert_keys,
upsert_fields: changeset.context[:private][:upsert_fields]
})
|> validate_manual_action_return_result!(
changeset.resource,
changeset.action
)
end
|> add_tenant(changeset)
|> manage_relationships(domain, changeset,
actor: opts[:actor],
authorize?: opts[:authorize?],
upsert?: opts[:upsert?]
)
else
belongs_to_attrs =
changeset.resource
|> Ash.Resource.Info.relationships()
|> Enum.filter(&(&1.type == :belongs_to))
|> Enum.map(& &1.source_attribute)
final_check =
changeset.resource
|> Ash.Resource.Info.attributes()
|> Enum.reject(
&(&1.allow_nil? || &1.generated? || &1.name in belongs_to_attrs)
)
changeset =
if changeset.context[:private][:action_result] do
changeset
else
changeset =
changeset
|> Ash.Changeset.require_values(
:create,
true,
final_check
)
{changeset, _} =
Ash.Actions.ManagedRelationships.validate_required_belongs_to(
{changeset, []}
)
changeset
end
if changeset.valid? do
cond do
result = changeset.context[:private][:action_result] ->
result
|> add_tenant(changeset)
|> manage_relationships(domain, changeset,
actor: opts[:actor],
authorize?: opts[:authorize?],
upsert?: opts[:upsert?]
)
opts[:upsert?] ->
changeset.resource
|> Ash.DataLayer.upsert(
changeset,
upsert_keys,
(opts[:upsert_identity] || changeset.action.upsert_identity) &&
Ash.Resource.Info.identity(
changeset.resource,
opts[:upsert_identity] || changeset.action.upsert_identity
)
)
|> case do
{:ok, {:upsert_skipped, _query, callback}} ->
if opts[:return_skipped_upsert?] do
callback.()
else
{:error,
Ash.Error.Changes.StaleRecord.exception(
resource: changeset.resource,
filter: changeset.filter
)}
end
{:ok, %{__metadata__: %{upsert_skipped: true}}} = result ->
if opts[:return_skipped_upsert?] do
result
else
{:error,
Ash.Error.Changes.StaleRecord.exception(
resource: changeset.resource,
filter: changeset.filter
)}
end
other ->
other
end
|> Helpers.rollback_if_in_transaction(
changeset.resource,
changeset
)
|> add_tenant(changeset)
|> manage_relationships(domain, changeset,
actor: opts[:actor],
authorize?: opts[:authorize?],
upsert?: opts[:upsert?]
)
true ->
case Ash.Changeset.handle_allow_nil_atomics(changeset, opts[:actor]) do
%Ash.Changeset{valid?: true} = changeset ->
changeset.resource
|> Ash.DataLayer.create(changeset)
|> Helpers.rollback_if_in_transaction(
changeset.resource,
changeset
)
|> add_tenant(changeset)
|> manage_relationships(domain, changeset,
actor: opts[:actor],
authorize?: opts[:authorize?],
upsert?: opts[:upsert?]
)
%Ash.Changeset{} = changeset ->
{:error, changeset}
end
end
|> case do
{:ok, result, instructions} ->
result =
Helpers.select(result, %{
resource: changeset.resource,
select: changeset.action_select
})
{:ok, result,
instructions
|> Map.update!(
:notifications,
&(&1 ++ manage_instructions.notifications)
)}
{:error, error} ->
{:error, Ash.Changeset.add_error(changeset, error)}
end
else
{:error, changeset}
end
end
else
{:error, changeset}
end
end
end,
transaction?: Keyword.get(opts, :transaction?, true) && changeset.action.transaction?,
rollback_on_error?: opts[:rollback_on_error?],
tracer: opts[:tracer],
notification_metadata: opts[:notification_metadata],
return_notifications?: opts[:return_notifications?],
transaction_metadata: %{
type: :create,
metadata: %{
resource: changeset.resource,
action: changeset.action.name,
actor: opts[:actor]
}
}
)
case result do
{:ok, created, changeset, instructions} ->
{:ok, created, instructions}
|> Helpers.load(
Ash.Changeset.set_context(changeset, %{
private: %{just_created_by_action: changeset.action.name}
}),
domain,
actor: opts[:actor],
action: Ash.Resource.Info.primary_action(changeset.resource, :read) || changeset.action,
reuse_values?: true,
authorize?: opts[:authorize?],
tracer: opts[:tracer]
)
|> Helpers.notify(changeset, opts)
|> Helpers.select(changeset)
|> Helpers.restrict_field_access(changeset)
{:error, error} ->
{:error, error}
end
end
defp validate_manual_action_return_result!({:ok, %resource{}} = result, resource, _) do
result
end
defp validate_manual_action_return_result!(
{:ok, %resource{}, notifications} = result,
resource,
_
)
when is_list(notifications) do
result
end
defp validate_manual_action_return_result!({:error, _error} = result, _resource, _) do
result
end
defp validate_manual_action_return_result!(other, resource, action) do
raise Ash.Error.Framework.InvalidReturnType,
message: """
Manual action #{inspect(action.name)} on #{inspect(resource)} returned an invalid result.
Expected one of the following:
* {:ok, %Resource{}}
* {:ok, %Resource{}, notifications}
* {:error, error}
Got:
#{inspect(other)}
"""
end
defp manage_relationships({:ok, nil}, _, _, _) do
{:ok, nil, %{notifications: []}}
end
defp manage_relationships(
{:ok, created, %{notifications: notifications}},
domain,
changeset,
engine_opts
) do
case manage_relationships({:ok, created}, domain, changeset, engine_opts) do
{:ok, created, info} ->
{:ok, created, Map.update(info, :notifications, notifications, &(&1 ++ notifications))}
other ->
other
end
end
defp manage_relationships({:ok, created}, domain, changeset, engine_opts) do
with {:ok, loaded} <-
Ash.Actions.ManagedRelationships.load(domain, created, changeset, engine_opts),
{:ok, with_relationships, new_notifications} <-
Ash.Actions.ManagedRelationships.manage_relationships(
loaded,
changeset,
engine_opts[:actor],
engine_opts
) do
{:ok, with_relationships, %{notifications: new_notifications, new_changeset: changeset}}
end
end
defp manage_relationships(other, _, _, _), do: other
defp set_tenant(changeset) do
changeset =
case changeset.data do
%{__metadata__: %{tenant: tenant}} ->
Ash.Changeset.set_tenant(changeset, tenant)
_ ->
changeset
end
if Helpers.get_multitenancy_from_context(changeset) in [:bypass, :bypass_all, :allow_global] do
changeset
else
case Helpers.validate_changeset_multitenancy(changeset) do
:ok -> handle_attribute_multitenancy(changeset)
{:error, error} -> Ash.Changeset.add_error(changeset, error)
end
end
end
defp handle_multitenancy(changeset, action) do
case Map.get(action, :multitenancy) || :enforce do
:enforce ->
changeset = handle_attribute_multitenancy(changeset)
case Helpers.validate_changeset_multitenancy(changeset) do
:ok -> changeset
{:error, error} -> Ash.Changeset.add_error(changeset, error)
end
:allow_global ->
changeset
|> handle_attribute_multitenancy()
|> Ash.Changeset.set_context(%{
shared: %{private: %{multitenancy: :allow_global}}
})
_ ->
Ash.Changeset.set_context(changeset, %{
shared: %{private: %{multitenancy: :bypass_all}}
})
end
end
defp handle_attribute_multitenancy(changeset) do
if changeset.tenant &&
Ash.Resource.Info.multitenancy_strategy(changeset.resource) == :attribute do
attribute = Ash.Resource.Info.multitenancy_attribute(changeset.resource)
{m, f, a} = Ash.Resource.Info.multitenancy_parse_attribute(changeset.resource)
attribute_value = apply(m, f, [changeset.to_tenant | a])
Ash.Changeset.force_change_attribute(changeset, attribute, attribute_value)
else
changeset
end
end
defp check_upsert_support(changeset, opts) do
if opts[:upsert?] && !Ash.DataLayer.data_layer_can?(changeset.resource, :upsert) do
Ash.Changeset.add_error(
changeset,
"Upsert is not supported by the data layer for this resource"
)
else
changeset
end
end
end