fix: run changesets w/ after_transaction hooks through standard logic

This commit is contained in:
Zach Daniel 2024-05-02 00:06:31 -04:00
parent 8a7a4763fd
commit c1354d503e
13 changed files with 321 additions and 71 deletions

View file

@ -144,7 +144,6 @@ defmodule Ash.Actions.Create.Bulk do
argument_names
)
)
|> reject_and_maybe_store_errors(ref, opts)
|> handle_batch(
domain,
resource,
@ -357,8 +356,38 @@ defmodule Ash.Actions.Create.Bulk do
opts[:tenant]
)
batch =
Enum.map(batch, &Ash.Changeset.run_before_transaction_hooks/1)
{batch, must_be_simple} =
batch
|> Stream.map(fn changeset ->
{changeset, _} =
Ash.Actions.ManagedRelationships.validate_required_belongs_to({changeset, []})
changeset
end)
|> Enum.reduce({[], []}, fn changeset, {batch, must_be_simple} ->
if changeset.after_transaction in [[], nil] do
changeset = Ash.Changeset.run_before_transaction_hooks(changeset)
{[changeset | batch], must_be_simple}
else
{batch, [%{changeset | __validated_for_action__: action.name} | must_be_simple]}
end
end)
must_be_simple_results =
Enum.flat_map(must_be_simple, fn changeset ->
case Ash.Actions.Create.run(domain, changeset, action, opts) do
{:ok, result} ->
[
Ash.Resource.set_metadata(result, %{
bulk_create_index: changeset.context.bulk_create.index
})
]
{:error, error} ->
store_error(ref, error, opts)
[]
end
end)
if opts[:transaction] == :batch &&
Ash.DataLayer.data_layer_can?(resource, :transact) do
@ -390,7 +419,8 @@ defmodule Ash.Actions.Create.Bulk do
data_layer_can_bulk?,
ref,
changes,
must_return_records_for_changes?
must_return_records_for_changes?,
must_be_simple_results
)
end,
opts[:timeout],
@ -435,7 +465,8 @@ defmodule Ash.Actions.Create.Bulk do
data_layer_can_bulk?,
ref,
changes,
must_return_records_for_changes?
must_return_records_for_changes?,
must_be_simple_results
)
end
end
@ -450,7 +481,8 @@ defmodule Ash.Actions.Create.Bulk do
data_layer_can_bulk?,
ref,
changes,
must_return_records_for_changes?
must_return_records_for_changes?,
must_be_simple_results
) do
must_return_records? =
opts[:notify?] ||
@ -492,6 +524,7 @@ defmodule Ash.Actions.Create.Bulk do
domain,
resource
)
|> Stream.concat(must_be_simple_results)
|> then(fn stream ->
if opts[:return_stream?] do
stream
@ -752,17 +785,6 @@ defmodule Ash.Actions.Create.Bulk do
end)
end
defp reject_and_maybe_store_errors(stream, ref, opts) do
Enum.reject(stream, fn changeset ->
if changeset.valid? do
false
else
store_error(ref, changeset, opts)
true
end
end)
end
defp store_error(_ref, empty, _opts) when empty in [[], nil], do: :ok
defp store_error(ref, error, opts) do
@ -778,7 +800,7 @@ defmodule Ash.Actions.Create.Bulk do
changeset
other ->
Ash.Error.to_ash_error(other)
Ash.Error.to_error_class(other)
end
Process.put(

View file

@ -804,7 +804,6 @@ defmodule Ash.Actions.Destroy.Bulk do
domain
)
)
|> reject_and_maybe_store_errors(ref, opts)
|> handle_batch(domain, resource, action, all_changes, opts, ref, base_changeset)
after
if opts[:notify?] && !opts[:return_notifications?] do
@ -1061,8 +1060,39 @@ defmodule Ash.Actions.Destroy.Bulk do
opts[:tenant]
)
batch =
Enum.map(batch, &Ash.Changeset.run_before_transaction_hooks/1)
{batch, must_be_simple} =
Enum.reduce(batch, {[], []}, fn changeset, {batch, must_be_simple} ->
if changeset.after_transaction in [[], nil] do
changeset = Ash.Changeset.run_before_transaction_hooks(changeset)
{[changeset | batch], must_be_simple}
else
{batch, [%{changeset | __validated_for_action__: action.name} | must_be_simple]}
end
end)
must_be_simple_results =
Enum.flat_map(must_be_simple, fn changeset ->
case Ash.Actions.Destroy.run(
domain,
changeset,
action,
Keyword.put(opts, :return_destroyed?, opts[:return_records?])
) do
:ok ->
[]
{:ok, result} when not is_list(result) ->
[
Ash.Resource.set_metadata(result, %{
bulk_destroy_index: changeset.context.bulk_destroy.index
})
]
{:error, error} ->
store_error(ref, error, opts)
[]
end
end)
if opts[:transaction] == :batch &&
Ash.DataLayer.data_layer_can?(resource, :transact) do
@ -1094,7 +1124,8 @@ defmodule Ash.Actions.Destroy.Bulk do
ref,
base_changeset,
must_return_records_for_changes?,
changes
changes,
must_be_simple_results
)
end,
opts[:timeout],
@ -1137,7 +1168,8 @@ defmodule Ash.Actions.Destroy.Bulk do
ref,
base_changeset,
must_return_records_for_changes?,
changes
changes,
must_be_simple_results
)
end
end
@ -1152,7 +1184,8 @@ defmodule Ash.Actions.Destroy.Bulk do
ref,
base_changeset,
must_return_records_for_changes?,
changes
changes,
must_be_simple_results
) do
must_return_records? =
opts[:notify?] ||
@ -1195,6 +1228,7 @@ defmodule Ash.Actions.Destroy.Bulk do
resource,
base_changeset
)
|> Stream.concat(must_be_simple_results)
|> then(fn stream ->
if opts[:return_stream?] do
stream
@ -1402,17 +1436,6 @@ defmodule Ash.Actions.Destroy.Bulk do
end)
end
defp reject_and_maybe_store_errors(stream, ref, opts) do
Enum.reject(stream, fn changeset ->
if changeset.valid? do
false
else
store_error(ref, changeset, opts)
true
end
end)
end
defp store_error(ref, errors, opts, count \\ nil)
defp store_error(ref, empty, _opts, error_count) when empty in [[], nil] do

View file

@ -8,6 +8,8 @@ defmodule Ash.Actions.Destroy do
@spec run(Ash.Domain.t(), Ash.Changeset.t(), Ash.Resource.Actions.action(), Keyword.t()) ::
{:ok, list(Ash.Notifier.Notification.t())}
| :ok
| {:ok, Ash.Resource.record()}
| {:ok, Ash.Resource.record(), list(Ash.Notifier.Notification.t())}
| {:error, Ash.Changeset.t()}
| {:error, term}
def run(domain, changeset, %{soft?: true} = action, opts) do

View file

@ -998,7 +998,6 @@ defmodule Ash.Actions.Update.Bulk do
context_key
)
)
|> reject_and_maybe_store_errors(ref, opts)
|> handle_batch(
domain,
resource,
@ -1299,8 +1298,31 @@ defmodule Ash.Actions.Update.Bulk do
context_key
)
batch =
Enum.map(batch, &Ash.Changeset.run_before_transaction_hooks/1)
{batch, must_be_simple} =
Enum.reduce(batch, {[], []}, fn changeset, {batch, must_be_simple} ->
if changeset.after_transaction in [[], nil] do
changeset = Ash.Changeset.run_before_transaction_hooks(changeset)
{[changeset | batch], must_be_simple}
else
{batch, [%{changeset | __validated_for_action__: action.name} | must_be_simple]}
end
end)
must_be_simple_results =
Enum.flat_map(must_be_simple, fn changeset ->
case Ash.Actions.Update.run(domain, changeset, action, opts) do
{:ok, result} ->
[
Ash.Resource.set_metadata(result, %{
metadata_key => changeset.context |> Map.get(context_key) |> Map.get(:index)
})
]
{:error, error} ->
store_error(ref, error, opts)
[]
end
end)
if opts[:transaction] == :batch &&
Ash.DataLayer.data_layer_can?(resource, :transact) do
@ -1338,7 +1360,8 @@ defmodule Ash.Actions.Update.Bulk do
context_key,
base_changeset,
must_return_records_for_changes?,
changes
changes,
must_be_simple_results
)
{new_errors, new_error_count} = Process.get({:bulk_update_errors, ref}) || {[], 0}
@ -1396,7 +1419,8 @@ defmodule Ash.Actions.Update.Bulk do
context_key,
base_changeset,
must_return_records_for_changes?,
changes
changes,
must_be_simple_results
)
end
end
@ -1413,7 +1437,8 @@ defmodule Ash.Actions.Update.Bulk do
context_key,
base_changeset,
must_return_records_for_changes?,
changes
changes,
must_be_simple_results
) do
must_return_records? =
opts[:notify?] ||
@ -1459,6 +1484,7 @@ defmodule Ash.Actions.Update.Bulk do
domain,
base_changeset
)
|> Stream.concat(must_be_simple_results)
|> then(fn stream ->
if opts[:return_stream?] do
stream
@ -1684,17 +1710,6 @@ defmodule Ash.Actions.Update.Bulk do
end)
end
defp reject_and_maybe_store_errors(stream, ref, opts) do
Enum.reject(stream, fn changeset ->
if changeset.valid? do
false
else
store_error(ref, changeset, opts)
true
end
end)
end
defp store_error(ref, errors, opts, count \\ nil)
defp store_error(_ref, empty, _opts, 0) when empty in [[], nil], do: :ok

View file

@ -11,8 +11,20 @@ defmodule Ash.Actions.Update do
| {:ok, Ash.Resource.record()}
| {:error, Ash.Changeset.t()}
| {:error, term}
def run(domain, %{valid?: false} = changeset, _action, _opts) do
{:error, Ash.Error.to_error_class(changeset.errors, changeset: %{changeset | domain: domain})}
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
@ -51,6 +63,10 @@ defmodule Ash.Actions.Update do
{{:not_atomic, "cannot atomically run a changeset with an around_transaction hook"},
nil}
!Enum.empty?(changeset.after_transaction) ->
{{:not_atomic, "cannot atomically run a changeset with an after_transaction hook"},
nil}
!primary_read ->
{{:not_atomic, "cannot atomically update a record without a primary read action"},
nil}

View file

@ -555,7 +555,8 @@ defmodule Ash.Changeset do
changeset = set_phase(changeset, :atomic)
with :ok <- verify_notifiers_support_atomic(resource, action),
%Ash.Changeset{} = changeset <- atomic_params(changeset, action, params, opts),
%Ash.Changeset{} = changeset <-
atomic_params(changeset, action, params, opts),
%Ash.Changeset{} = changeset <- atomic_changes(changeset, action),
%Ash.Changeset{} = changeset <- atomic_defaults(changeset),
%Ash.Changeset{} = changeset <- atomic_update(changeset, opts[:atomic_update] || []),
@ -1393,7 +1394,12 @@ defmodule Ash.Changeset do
case Ash.Type.cast_atomic(attribute.type, value, attribute.constraints) do
{:atomic, value} ->
value = set_error_field(value, attribute.name)
value =
if attribute.primary_key? do
value
else
set_error_field(value, attribute.name)
end
%{changeset | atomics: Keyword.put(changeset.atomics, key, value)}
|> record_atomic_update_for_atomic_upgrade(attribute.name, value)
@ -3272,7 +3278,7 @@ defmodule Ash.Changeset do
)
|> case do
{:ok, new_result} ->
{:ok, %{new_result | after_transaction: []}}
{:ok, new_result}
{:error, error} ->
{:error, error}

View file

@ -42,6 +42,23 @@ defmodule Ash.Error do
end
def to_error_class(value, opts) do
value =
value
|> List.wrap()
|> Enum.map(fn
%Ash.Changeset{} = changeset ->
to_error_class(changeset, opts)
%Ash.Query{} = query ->
to_error_class(query, opts)
%Ash.ActionInput{} = action_input ->
to_error_class(action_input, opts)
other ->
other
end)
class = to_class(value, opts)
class =

View file

@ -36,7 +36,7 @@
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
"sourceror": {:hex, :sourceror, "1.0.3", "111711c147f4f1414c07a67b45ad0064a7a41569037355407eda635649507f1d", [:mix], [], "hexpm", "56c21ef146c00b51bc3bb78d1f047cb732d193256a7c4ba91eaf828d3ae826af"},
"spark": {:hex, :spark, "2.1.20", "204db8fd28378783c28a9dcb0bebdaf1d51b14a9ea106e1080457d29510a66ea", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e7a4f8f8ca7a477918af1eb65e20f2015f783a9a23e5f73d1020edf5b2ef69be"},
"splode": {:hex, :splode, "0.2.3", "43a851790699c0993787d92bff017eb36f33ad6544974e47f7643f24ff89ac80", [:mix], [], "hexpm", "c91dc334647b5af4dc65b304635372df3d24c55bc389f9390cbb69d1c5bfd3e0"},
"splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},

View file

@ -89,9 +89,10 @@ defmodule Ash.Test.Actions.BulkCreateTest do
create :create_with_change do
change fn changeset, _ ->
title = Ash.Changeset.get_attribute(changeset, :title)
Ash.Changeset.force_change_attribute(changeset, :title, title <> "_stuff")
end
title = Ash.Changeset.get_attribute(changeset, :title)
Ash.Changeset.force_change_attribute(changeset, :title, title <> "_stuff")
end,
only_when_valid?: true
end
create :create_with_argument do
@ -201,11 +202,17 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "runs changes" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{records: [%{title: "title1_stuff"}, %{title: "title2_stuff"}]} =
Ash.bulk_create!(
[%{title: "title1"}, %{title: "title2"}],
Post,
:create_with_change,
tenant: org.id,
return_records?: true,
authorize?: false,
sorted?: true
@ -213,18 +220,29 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "accepts arguments" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{records: [%{title: "title1"}, %{title: "title2"}]} =
Ash.bulk_create!(
[%{a_title: "title1"}, %{a_title: "title2"}],
Post,
:create_with_argument,
return_records?: true,
tenant: org.id,
sorted?: true,
authorize?: false
)
end
test "runs after batch hooks" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{
records: [%{title: "before_title1_after"}, %{title: "before_title2_after"}]
} =
@ -232,6 +250,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
[%{title: "title1"}, %{title: "title2"}],
Post,
:create_with_after_batch,
tenant: org.id,
return_records?: true,
sorted?: true,
authorize?: false
@ -239,11 +258,17 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "will return error count" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{records: [%{title: "title1_stuff"}], error_count: 1, errors: nil} =
Ash.bulk_create!(
[%{title: "title1"}, %{title: %{foo: :bar}}],
Post,
:create_with_change,
tenant: org.id,
return_records?: true,
sorted?: true,
authorize?: false
@ -251,6 +276,11 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "will return errors on request" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{
records: [%{title: "title1_stuff"}],
error_count: 1,
@ -260,6 +290,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
[%{title: "title1"}, %{title: %{foo: :bar}}],
Post,
:create_with_change,
tenant: org.id,
return_records?: true,
return_errors?: true,
sorted?: true,
@ -268,6 +299,11 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "can upsert with list" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{
records: [
%{title: "title1", title2: "changes", title3: "wont"},
@ -281,6 +317,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
],
Post,
:create,
tenant: org.id,
return_records?: true,
sorted?: true,
authorize?: false
@ -300,6 +337,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
Post,
:create,
return_records?: true,
tenant: org.id,
upsert?: true,
upsert_identity: :unique_title,
upsert_fields: [:title2],
@ -309,6 +347,11 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "can upsert with :replace" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{
records: [
%{title: "title1", title2: "changes", title3: "wont"},
@ -322,6 +365,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
],
Post,
:create,
tenant: org.id,
return_records?: true,
sorted?: true,
authorize?: false
@ -342,6 +386,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
:create,
return_records?: true,
upsert?: true,
tenant: org.id,
upsert_identity: :unique_title,
upsert_fields: {:replace, [:title2]},
sorted?: true,
@ -350,6 +395,11 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "can upsert with :replace_all" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{
records: [
%{title: "title1", title2: "changes", title3: "changes"},
@ -364,6 +414,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
Post,
:create,
return_records?: true,
tenant: org.id,
sorted?: true,
authorize?: false
)
@ -382,6 +433,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
Post,
:create,
return_records?: true,
tenant: org.id,
upsert?: true,
upsert_identity: :unique_title,
upsert_fields: :replace_all,
@ -391,6 +443,11 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "can upsert with :replace_all_except" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{
records: [
%{title: "title1", title2: "changes", title3: "wont"},
@ -404,6 +461,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
],
Post,
:create,
tenant: org.id,
return_records?: true,
sorted?: true,
authorize?: false
@ -423,6 +481,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
Post,
:create,
return_records?: true,
tenant: org.id,
upsert?: true,
upsert_identity: :unique_title,
upsert_fields: {:replace_all_except, [:title, :title3]},
@ -432,6 +491,11 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "runs before transaction hooks" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{
records: [
%{title: "before_transaction_title1"},
@ -442,6 +506,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
[%{title: "title1"}, %{title: "title2"}],
Post,
:create_with_before_transaction,
tenant: org.id,
return_records?: true,
sorted?: true,
authorize?: false
@ -449,11 +514,17 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "runs after action hooks" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{records: [%{title: "title1_stuff"}, %{title: "title2_stuff"}]} =
Ash.bulk_create!(
[%{title: "title1"}, %{title: "title2"}],
Post,
:create_with_after_action,
tenant: org.id,
return_records?: true,
sorted?: true,
authorize?: false
@ -461,25 +532,40 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "runs after transaction hooks on success" do
assert %Ash.BulkResult{records: [%{title: "title1_stuff"}, %{title: "title2_stuff"}]} =
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{
records: [%{title: "title1_stuff"}, %{title: "title2_stuff"}]
} =
Ash.bulk_create!(
[%{title: "title1"}, %{title: "title2"}],
Post,
:create_with_after_transaction,
tenant: org.id,
return_records?: true,
return_errors?: true,
sorted?: true,
authorize?: false
)
end
test "runs after transaction hooks on failure" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{error_count: 2} =
Ash.bulk_create(
[%{title: 1}, %{title: 2}],
Post,
:create_with_after_transaction,
sorted?: true,
authorize?: false
authorize?: false,
tenant: org.id
)
assert_receive {:error, _error}
@ -488,18 +574,30 @@ defmodule Ash.Test.Actions.BulkCreateTest do
describe "authorization" do
test "policy success results in successes" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{records: [%{title: "title1"}, %{title: "title2"}]} =
Ash.bulk_create!(
[%{title: "title1", authorize?: true}, %{title: "title2", authorize?: true}],
Post,
:create_with_policy,
tenant: org.id,
authorize?: true,
return_errors?: true,
return_records?: true,
sorted?: true
)
end
test "field authorization is run" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{
records: [
%{hidden_attribute: %Ash.ForbiddenField{}, hidden_calc: %Ash.ForbiddenField{}},
@ -507,10 +605,14 @@ defmodule Ash.Test.Actions.BulkCreateTest do
]
} =
Ash.bulk_create!(
[%{title: "title1", authorize?: true}, %{title: "title2", authorize?: true}],
[
%{title: "title1", authorize?: true},
%{title: "title2", authorize?: true}
],
Post,
:create_with_policy,
authorize?: true,
tenant: org.id,
return_records?: true,
sorted?: true,
load: [:hidden_calc]
@ -518,9 +620,17 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "policy failure results in failures" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert %Ash.BulkResult{errors: [_, _]} =
Ash.bulk_create(
[%{title: "title1", authorize?: false}, %{title: "title2", authorize?: false}],
[
%{title: "title1", authorize?: false, org_id: org.id},
%{title: "title2", authorize?: false, org_id: org.id}
],
Post,
:create_with_policy,
authorize?: true,
@ -533,8 +643,16 @@ defmodule Ash.Test.Actions.BulkCreateTest do
describe "streaming" do
test "by default nothing is returned in the stream" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert [] =
[%{title: "title1", authorize?: true}, %{title: "title2", authorize?: true}]
[
%{title: "title1", authorize?: true, org_id: org.id},
%{title: "title2", authorize?: true, org_id: org.id}
]
|> Ash.bulk_create!(
Post,
:create_with_policy,
@ -545,11 +663,17 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "batch size is honored while streaming" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert [_] =
[%{title: "title1", authorize?: true}, %{title: "title2", authorize?: true}]
|> Ash.bulk_create!(
Post,
:create_with_policy,
tenant: org.id,
authorize?: true,
batch_size: 1,
return_records?: true,
@ -561,11 +685,17 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "by returning notifications, you get the notifications in the stream" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert [{:notification, _}, {:notification, _}] =
[%{title: "title1", authorize?: true}, %{title: "title2", authorize?: true}]
|> Ash.bulk_create!(
Post,
:create_with_policy,
tenant: org.id,
authorize?: true,
return_stream?: true,
notify?: true,
@ -575,12 +705,18 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "by returning records, you get the records in the stream" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert [{:ok, %{title: "title1"}}, {:ok, %{title: "title2"}}] =
[%{title: "title1", authorize?: true}, %{title: "title2", authorize?: true}]
|> Ash.bulk_create!(
Post,
:create_with_policy,
authorize?: true,
tenant: org.id,
return_stream?: true,
return_records?: true
)
@ -595,6 +731,11 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "by returning notifications and records, you get them both in the stream" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert [
{:notification, _},
{:notification, _},
@ -606,6 +747,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
Post,
:create_with_policy,
authorize?: true,
tenant: org.id,
notify?: true,
return_stream?: true,
return_notifications?: true,
@ -625,6 +767,11 @@ defmodule Ash.Test.Actions.BulkCreateTest do
end
test "any errors are also returned in the stream" do
org =
Org
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
assert [
{:error, %Ash.Changeset{}},
{:notification, _},
@ -639,6 +786,7 @@ defmodule Ash.Test.Actions.BulkCreateTest do
:create_with_policy,
authorize?: true,
notify?: true,
tenant: org.id,
return_stream?: true,
return_notifications?: true,
return_records?: true,

View file

@ -149,6 +149,7 @@ defmodule Ash.Test.Actions.BulkUpdateTest do
_changeset, {:error, error}, _context ->
send(self(), {:error, error})
{:error, error}
end)
end

View file

@ -98,13 +98,13 @@ defmodule Ash.Test.Actions.ValidationTest do
|> Ash.Changeset.for_create(:create, %{foo: true, status: "foo"})
|> Ash.create!()
assert_raise(Ash.Error.Invalid, ~r/status: must not equal foo/, fn ->
assert_raise(Ash.Error.Invalid, ~r/status: must not equal \"foo\"/, fn ->
Profile
|> Ash.Changeset.for_create(:create, %{foo: false, status: "foo"})
|> Ash.create!()
end)
assert_raise(Ash.Error.Invalid, ~r/status: must equal foo/, fn ->
assert_raise(Ash.Error.Invalid, ~r/status: must equal \"foo\"/, fn ->
Profile
|> Ash.Changeset.for_create(:create, %{foo: true, status: "bar"})
|> Ash.create!()
@ -164,7 +164,7 @@ defmodule Ash.Test.Actions.ValidationTest do
end
test "it fails if the value is not in the list" do
assert_raise(Ash.Error.Invalid, ~r/expected one of foo, bar/, fn ->
assert_raise(Ash.Error.Invalid, ~r/expected one of \"foo, bar\"/, fn ->
Profile
|> Ash.Changeset.for_create(:create, %{status: "blart"})
|> Ash.create!()

View file

@ -322,7 +322,7 @@ defmodule Ash.Test.Changeset.EmbeddedResourceTest do
test "embedded resources run validations on create" do
msg =
~r/Invalid value provided for last_name: exactly 2 of first_name,last_name must be present/
~r/Invalid value provided for last_name: exactly 2 of "first_name,last_name" must be present/
assert_raise Ash.Error.Invalid,
msg,

View file

@ -37,7 +37,7 @@ defmodule Ash.Test.ErrorTest do
end
test "returns exception if it is a map/struct with class: :special wrapped in a list" do
assert [%{class: :special}] =
assert %{class: :special} =
Ash.Error.to_error_class([SpecialError.exception([])], [])
end