Packages
ash
3.29.2
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
import Ash.Expr
@spec run(Ash.Domain.t(), Ash.Resource.Record.t(), Ash.Resource.Actions.action(), Keyword.t()) ::
:ok
| {:ok, Ash.Resource.Record.t(), list(Ash.Notifier.Notification.t())}
| {:ok, Ash.Resource.Record.t()}
| {: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,
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,
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,
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_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} ->
# Recompute changed? after setup_managed_belongs_to_relationships
# may have set FK attributes via force_change_attribute (e.g. when
# manage_relationship is called from before_transaction hooks on
# update actions). Without this, the changed? flag computed before
# the func callback would be false, causing the DB update to be
# skipped entirely.
changed? =
Ash.Changeset.changing_attributes?(changeset) or
not Enum.empty?(changeset.atomics)
changeset =
changeset
|> Ash.Changeset.put_context(
:changed?,
changeset.context[:changed?] || changed?
)
|> 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)
|> then(fn cs ->
Ash.Resource.ManualUpdate.update(
mod,
cs,
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?]
)
end
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, :no_rollback, error} ->
{:error, :no_rollback, error}
{:error, error} ->
{:error, error}
_ ->
{: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: _notifications}} = result,
resource,
_
) 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