Packages
ash
3.13.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
Current section
Files
lib/ash/actions/update/update.ex
# SPDX-FileCopyrightText: 2019 ash contributors <https://github.com/ash-project/ash/graphs.contributors>
#
# SPDX-License-Identifier: MIT
defmodule Ash.Actions.Update do
@moduledoc false
alias Ash.Actions.Helpers
require Ash.Tracer
require Logger
import Ash.Expr
@spec run(Ash.Domain.t(), Ash.Resource.record(), Ash.Resource.Actions.action(), Keyword.t()) ::
:ok
| {:ok, Ash.Resource.record(), list(Ash.Notifier.Notification.t())}
| {:ok, Ash.Resource.record()}
| {:error, Ash.Changeset.t()}
| {:error, term}
def run(domain, %{valid?: false, errors: errors} = changeset, action, opts) do
changeset = changeset(changeset, domain, action, opts)
errors = Helpers.process_errors(changeset, errors)
case Ash.Changeset.run_after_transactions(
{:error, Ash.Error.to_error_class(errors, changeset: changeset)},
changeset
) do
{:ok, result} ->
{:ok, result}
{:error, error} ->
{:error, Ash.Error.to_error_class(error, changeset: %{changeset | domain: domain})}
end
end
def run(domain, changeset, action, opts) do
if changeset.atomics != [] &&
!Ash.DataLayer.data_layer_can?(changeset.resource, {:atomic, :update}) do
{:error,
Ash.Error.Invalid.AtomicsNotSupported.exception(
resource: changeset.resource,
action_type: :update
)}
else
changeset =
if Map.get(action, :multitenancy) in [:bypass, :bypass_all] do
Ash.Changeset.set_context(changeset, %{
shared: %{private: %{multitenancy: :bypass_all}}
})
else
changeset
end
atomic_upgrade_read =
case action.atomic_upgrade_with do
nil ->
Ash.Resource.Info.primary_action(changeset.resource, :read)
atomic_upgrade_with ->
Ash.Resource.Info.action(changeset.resource, atomic_upgrade_with) ||
raise ArgumentError,
"#{inspect(changeset.resource)}.atomic_upgrade_with is set to #{atomic_upgrade_with}, which is not a valid action"
end
dirty_hooks = changeset.dirty_hooks -- [:after_action, :after_transaction]
{fully_atomic_changeset, params} =
cond do
!action.require_atomic? && !action.atomic_upgrade? ->
{{:not_atomic, "action has `atomic_upgrade? false`"}, nil}
!Ash.DataLayer.data_layer_can?(changeset.resource, :expr_error) && opts[:authorize?] ->
{{:not_atomic, "data layer does not support adding errors to a query"}, nil}
!Ash.DataLayer.data_layer_can?(changeset.resource, :update_query) ->
{{:not_atomic, "data layer does not support updating a query"}, nil}
:manage_relationships in changeset.dirty_hooks ->
{{:not_atomic, "cannot atomically manage relationships"}, nil}
!Enum.empty?(dirty_hooks) ->
{{:not_atomic,
"cannot atomically run a changeset with hooks in any phase other than `after_action` or `after_transaction`, got hooks in phases #{inspect(dirty_hooks)}"},
nil}
!atomic_upgrade_read ->
{{:not_atomic,
"cannot atomically update a record without a primary read action or a configured `atomic_upgrade_with` action"},
nil}
opts[:atomic_upgrade?] == false ->
{{:not_atomic, "atomic upgrade was disabled with opts"}, nil}
true ->
params =
changeset.casted_attributes
|> Map.merge(changeset.arguments)
|> Map.merge(changeset.casted_arguments)
res =
Ash.Changeset.fully_atomic_changeset(
changeset.resource,
action,
params,
opts
|> Keyword.merge(
context: changeset.context_changes,
notify?: true,
data: changeset.data,
assume_casted?: true,
no_atomic_constraints: changeset.no_atomic_constraints,
atomics:
Keyword.merge(
changeset.atomic_changes,
Keyword.new(changeset.attribute_changes)
),
tenant: changeset.tenant
)
)
|> then(fn
%Ash.Changeset{} = atomic_changeset ->
atomic_changeset
|> then(fn atomic_changeset ->
Enum.reduce(
changeset.atomic_after_action,
atomic_changeset,
fn after_action, atomic_changeset ->
Ash.Changeset.after_action(atomic_changeset, after_action)
end
)
end)
|> then(fn atomic_changeset ->
Enum.reduce(
changeset.atomic_after_transaction,
atomic_changeset,
fn after_transaction, atomic_changeset ->
Ash.Changeset.after_transaction(atomic_changeset, after_transaction)
end
)
end)
other ->
other
end)
{res, params}
end
case fully_atomic_changeset do
%Ash.Changeset{} = atomic_changeset ->
atomic_changeset =
%{atomic_changeset | data: changeset.data}
|> Ash.Changeset.set_context(%{data_layer: %{use_atomic_update_data?: true}})
|> Map.put(:load, changeset.load)
|> Map.put(:select, changeset.select)
|> Map.put(:filter, changeset.added_filter)
|> Ash.Changeset.set_context(changeset.context)
{atomic_changeset, opts} =
Ash.Actions.Helpers.set_context_and_get_opts(domain, atomic_changeset, opts)
atomic_changeset = Helpers.apply_opts_load(atomic_changeset, opts)
opts =
Keyword.merge(opts,
atomic_changeset: atomic_changeset,
return_records?: true,
notify?: true,
return_notifications?: opts[:return_notifications?],
return_errors?: true
)
primary_key = Ash.Resource.Info.primary_key(atomic_changeset.resource)
primary_key_filter = changeset.data |> Map.take(primary_key) |> Map.to_list()
query =
atomic_changeset.resource
|> Ash.Query.set_context(%{private: %{internal?: true}})
|> Ash.Query.for_read(atomic_upgrade_read.name, %{},
actor: opts[:actor],
authorize?: false,
context: atomic_changeset.context,
tenant: atomic_changeset.tenant,
tracer: opts[:tracer]
)
|> Ash.Query.do_filter(primary_key_filter)
authorize_changeset_with =
if Ash.DataLayer.data_layer_can?(atomic_changeset.resource, :expr_error) do
:error
else
:filter
end
case Ash.Actions.Update.Bulk.run(
domain,
query,
fully_atomic_changeset.action,
params,
Keyword.merge(opts,
strategy: [:atomic, :stream],
resource: atomic_changeset.resource,
read_action: atomic_upgrade_read.name,
tenant: atomic_changeset.tenant,
authorize_query?: false,
return_records?: true,
atomic_changeset: atomic_changeset,
authorize_changeset_with: authorize_changeset_with
)
) do
%Ash.BulkResult{status: :success, records: [record], notifications: notifications} ->
if opts[:return_notifications?] do
{:ok, record, List.wrap(notifications)}
else
{:ok, record}
end
%Ash.BulkResult{status: :success, records: []} ->
primary_key = Ash.Resource.Info.primary_key(atomic_changeset.resource)
{:error,
Ash.Error.to_error_class(
Ash.Error.Changes.StaleRecord.exception(
resource: fully_atomic_changeset.resource,
filter: Map.take(changeset.data, primary_key)
),
changeset: atomic_changeset
)}
%Ash.BulkResult{status: :error, errors: errors} ->
{:error, Ash.Error.to_error_class(errors, changeset: atomic_changeset)}
end
other ->
if Ash.DataLayer.data_layer_can?(changeset.resource, :update_query) &&
Ash.DataLayer.data_layer_can?(changeset.resource, :expr_error) &&
action.require_atomic? &&
match?({:not_atomic, _reason}, other) do
{:not_atomic, reason} = other
{:error,
Ash.Error.Framework.MustBeAtomic.exception(
resource: changeset.resource,
action: action.name,
reason: reason
)}
else
{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), :update],
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
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
@doc false
def do_run(domain, changeset, action, opts) do
with %{valid?: true} = changeset <- handle_multitenancy(changeset, action),
%{valid?: true} = changeset <- changeset(changeset, domain, action, opts),
%{valid?: true} = changeset <- authorize(changeset, opts),
%{valid?: true} = changeset <-
Ash.Changeset.add_atomic_validations(changeset, opts[:actor], []),
{:ok, result, instructions} <- commit(changeset, domain, opts) do
add_notifications(
changeset.resource,
result,
changeset.action,
changeset,
instructions,
opts[:return_notifications?],
opts[:return_destroyed?]
)
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,
return_forbidden_error?: true,
pre_flight?: false,
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, 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, _changeset) do
other
end
defp add_notifications(
resource,
result,
action,
changeset,
instructions,
return_notifications?,
return_destroyed?
) do
if return_notifications? do
if changeset.action_type == :destroy && !return_destroyed? do
{:ok, Map.get(instructions, :notifications, [])}
else
{:ok, result, Map.get(instructions, :notifications, [])}
end
else
Helpers.warn_missed!(resource, action, instructions)
if changeset.action_type == :destroy && !return_destroyed? do
:ok
else
{:ok, result}
end
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_update(changeset, action.name, %{}, opts)
end
|> Ash.Changeset.timeout(opts[:timeout] || changeset.timeout)
end
defp commit(changeset, domain, opts) do
can_atomic_update? =
Ash.DataLayer.data_layer_can?(changeset.resource, {:atomic, :update})
result =
changeset
|> then(fn changeset ->
if Enum.empty?(changeset.relationships) do
changeset
else
Ash.Changeset.before_action(
changeset,
&Ash.Actions.ManagedRelationships.setup_managed_belongs_to_relationships(
&1,
opts[:actor],
authorize?: opts[:authorize?],
actor: opts[:actor]
)
)
end
end)
|> Ash.Changeset.with_hooks(
fn
%{atomics: atomics} when atomics != [] and not can_atomic_update? ->
{:error,
Ash.Error.Invalid.AtomicsNotSupported.exception(
resource: changeset.resource,
action_type: :update
)}
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],
actor: opts[:actor],
authorize?: opts[:authorize?]
) do
{:error, error} ->
{:error, error}
{changeset, manage_instructions} ->
changeset =
changeset
|> Ash.Changeset.require_values(
:update,
true
)
|> Ash.Changeset.require_values(
:update,
false,
changeset.action.require_attributes
)
changeset = set_tenant(changeset)
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
changeset
|> Ash.Changeset.set_defaults(:update, true)
|> mod.update(
action_opts,
%Ash.Resource.ManualUpdate.Context{
select: changeset.select,
actor: opts[:actor],
source_context: changeset.context,
tenant: changeset.tenant,
authorize?: opts[:authorize?],
domain: changeset.domain
}
)
|> validate_manual_action_return_result!(
changeset.resource,
changeset.action
)
end
|> manage_relationships(domain, changeset,
actor: opts[:actor],
authorize?: opts[:authorize?]
)
else
cond do
result = changeset.context[:private][:action_result] ->
result
|> add_tenant(changeset)
|> manage_relationships(domain, changeset,
actor: opts[:actor],
authorize?: opts[:authorize?]
)
changeset.context.changed? ->
changeset =
if Enum.empty?(changeset.attributes) &&
Ash.DataLayer.data_layer_can?(changeset.resource, :atomic_update) do
Ash.Changeset.atomic_defaults(changeset)
else
Ash.Changeset.set_defaults(changeset, :update, true)
end
case Ash.Changeset.handle_allow_nil_atomics(changeset, opts[:actor]) do
%Ash.Changeset{valid?: true} = changeset ->
changeset.resource
|> Ash.DataLayer.update(changeset)
|> case do
{:ok, data} ->
{:ok, %{data | __metadata__: changeset.data.__metadata__}}
{:error, :no_rollback, error} ->
{:error, :no_rollback, error}
{:error, error} ->
{:error, error}
end
|> Helpers.rollback_if_in_transaction(
changeset.resource,
changeset
)
|> add_tenant(changeset)
|> manage_relationships(domain, changeset,
actor: opts[:actor],
authorize?: opts[:authorize?]
)
%Ash.Changeset{valid?: false} = changeset ->
{:error, changeset}
end
true ->
result =
if is_nil(changeset.filter) do
{:ok, changeset.data}
else
{:ok, record_filter} =
Ash.Filter.get_filter(changeset.resource, changeset.data)
changeset.resource
|> Ash.Query.do_filter(changeset.filter)
|> Ash.Query.do_filter(record_filter)
|> Ash.Query.select(changeset.action_select)
|> Ash.Query.data_layer_query()
|> case do
{:ok, data_layer_query} ->
data_layer_query
|> Ash.DataLayer.run_query(changeset.resource)
|> case do
{:ok, [record]} ->
attribute_names =
Ash.Resource.Info.attribute_names(changeset.resource)
{:ok,
Map.merge(
changeset.data,
Map.take(record, Enum.to_list(attribute_names))
)}
_ ->
{:error,
Ash.Error.Changes.StaleRecord.exception(
resource: changeset.resource
)}
end
{:error, error} ->
{:error, error}
end
end
result
|> Helpers.rollback_if_in_transaction(
changeset.resource,
changeset
)
|> add_tenant(changeset)
|> manage_relationships(domain, changeset,
actor: opts[:actor],
authorize?: opts[:authorize?]
)
end
end
|> case do
{:ok, result} ->
result =
Helpers.select(result, %{
resource: changeset.resource,
select: changeset.action_select
})
{:ok, result, %{notifications: manage_instructions.notifications}}
{:ok, result, notifications} ->
result =
Helpers.select(result, %{
resource: changeset.resource,
select: changeset.action_select
})
{:ok, result,
Map.update!(
notifications,
:notifications,
&(&1 ++ manage_instructions.notifications)
)}
{:error, error} ->
{:error, Ash.Changeset.add_error(changeset, error)}
end
else
{:error, changeset}
end
end
end,
transaction?: Keyword.get(opts, :transaction?, true) && changeset.action.transaction?,
rollback_on_error?: opts[:rollback_on_error?],
notification_metadata: opts[:notification_metadata],
return_notifications?: opts[:return_notifications?],
transaction_metadata: %{
type: :update,
metadata: %{
record: changeset.data,
resource: changeset.resource,
action: changeset.action.name,
actor: opts[:actor]
}
}
)
case result do
{:ok, updated, changeset, instructions} ->
{:ok, updated, instructions}
|> Helpers.load(changeset, domain,
actor: opts[:actor],
reuse_values?: true,
action: Ash.Resource.Info.primary_action(changeset.resource, :read) || changeset.action,
authorize?: opts[:authorize?],
tracer: opts[:tracer]
)
|> Helpers.notify(changeset, opts)
|> Helpers.select(changeset)
|> Helpers.restrict_field_access(changeset)
{:error, changeset} ->
{:error, changeset}
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, updated, %{notifications: notifications}},
domain,
changeset,
engine_opts
) do
case manage_relationships({:ok, updated}, domain, changeset, engine_opts) do
{:ok, updated, info} ->
{:ok, updated, Map.update(info, :notifications, notifications, &(&1 ++ notifications))}
other ->
other
end
end
defp manage_relationships({:ok, updated}, domain, changeset, engine_opts) do
with {:ok, loaded} <-
Ash.Actions.ManagedRelationships.load(domain, updated, 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}}
end
end
defp manage_relationships(other, _, _, _), do: other
defp set_tenant(changeset) do
if Helpers.get_multitenancy_from_context(changeset) in [:bypass, :bypass_all] 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 ->
handle_attribute_multitenancy(changeset)
_ ->
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.filter(changeset, expr(^ref(attribute) == ^attribute_value))
else
changeset
end
end
end