fix: ensure we retain the sort order of embedded resources

fixes #883
This commit is contained in:
Zach Daniel 2024-02-13 13:55:07 -05:00
parent fe0058ce98
commit 3d30fd848f
7 changed files with 36 additions and 41 deletions

View file

@ -167,6 +167,7 @@ spark_locals_without_parens = [
prefix: 1,
prepare: 1,
prepare: 2,
previous_values?: 1,
primary?: 1,
primary_key?: 1,
private?: 1,

View file

@ -39,12 +39,6 @@ Resource
|> Ash.Changeset.for_create(:create, %{embeds: embeds_input, maps: embeds_input})
|> Api.create!()
# :eflame.apply(fn ->
# Resource
# |> Ash.Changeset.for_create(:create, %{embeds: embeds_input})
# |> Api.create!()
# end, [])
Benchee.run(
%{
embeds: fn ->

View file

@ -80,6 +80,7 @@ publish :assign, "assigned"
| Name | Type | Default | Docs |
|------|------|---------|------|
| [`previous_values?`](#pub_sub-publish-previous_values?){: #pub_sub-publish-previous_values? } | `boolean` | `true` | Whether or not to publish messages with both the new values and the old values for referencing changed attributes |
| [`event`](#pub_sub-publish-event){: #pub_sub-publish-event } | `String.t` | | The name of the event to publish. Defaults to the action name |
| [`dispatcher`](#pub_sub-publish-dispatcher){: #pub_sub-publish-dispatcher } | `atom` | | The module to use as a dispatcher. If none is set, the pubsub module provided is used. |
@ -123,6 +124,7 @@ publish_all :create, "created"
| Name | Type | Default | Docs |
|------|------|---------|------|
| [`action`](#pub_sub-publish_all-action){: #pub_sub-publish_all-action } | `atom` | | The name of the action that should be published |
| [`previous_values?`](#pub_sub-publish_all-previous_values?){: #pub_sub-publish_all-previous_values? } | `boolean` | `true` | Whether or not to publish messages with both the new values and the old values for referencing changed attributes |
| [`event`](#pub_sub-publish_all-event){: #pub_sub-publish_all-event } | `String.t` | | The name of the event to publish. Defaults to the action name |
| [`dispatcher`](#pub_sub-publish_all-dispatcher){: #pub_sub-publish_all-dispatcher } | `atom` | | The module to use as a dispatcher. If none is set, the pubsub module provided is used. |

View file

@ -512,7 +512,12 @@ defmodule Ash.EmbeddableType do
|> Ash.EmbeddableType.copy_source(constraints[:__source__])
|> Ash.Query.load(constraints[:load] || [])
query = Ash.Query.sort(query, constraints[:sort])
query =
if constraints[:sort] do
Ash.Query.sort(query, constraints[:sort])
else
query
end
case ShadowApi.read(query) do
{:ok, result} ->

View file

@ -1,6 +1,9 @@
defmodule Ash.Policy.Check.ChangingAttributes do
@moduledoc "This check is true when attribute changes correspond to the provided options."
use Ash.Policy.SimpleCheck
use Ash.Policy.FilterCheckWithContext
import Ash.Filter.TemplateHelpers
require Ash.Expr
@impl true
def describe(opts) do
@ -26,43 +29,33 @@ defmodule Ash.Policy.Check.ChangingAttributes do
end
@impl true
def requires_original_data?(_, opts) do
Enum.any?(opts[:changing], fn
{_key, further} ->
Keyword.has_key?(further, :from)
def filter(_actor, %{changeset: %Ash.Changeset{} = changeset}, options) do
Enum.reduce_while(options[:changing], true, fn {attribute, opts}, expr ->
if Keyword.has_key?(opts, :from) && changeset.action_type == :create do
{:halt, false}
else
if Ash.Changeset.changing_attribute?(changeset, attribute) do
case {Keyword.fetch(opts, :from), Keyword.fetch(opts, :to)} do
{:error, :error} ->
{:cont, expr}
_ ->
false
end)
end
{{:ok, from}, {:ok, to}} ->
Ash.Expr.expr(
^expr and not (ref(attribute) == ^from and ^atomic_ref(attribute) == ^to)
)
@impl true
def match?(_actor, %{changeset: %Ash.Changeset{} = changeset}, opts) do
Enum.all?(opts[:changing], fn
{attribute, opts} ->
if Keyword.has_key?(opts, :from) && changeset.action_type == :create do
false
else
case Ash.Changeset.fetch_change(changeset, attribute) do
{:ok, new_value} ->
opts == [] ||
Enum.all?(opts, fn
{:to, value} ->
new_value == value
{{:ok, from}, :error} ->
Ash.Expr.expr(^expr and ref(attribute) != ^from)
{:from, value} ->
Map.get(changeset.data, attribute) == value
end)
_ ->
false
{:error, {:ok, to}} ->
Ash.Expr.expr(^expr and ^atomic_ref(attribute) != ^to)
end
else
{:cont, expr}
end
attribute ->
match?({:ok, _}, Ash.Changeset.fetch_change(changeset, attribute))
end
end)
end
def match?(_, _, _), do: false
def filter(_, _, _), do: false
end

View file

@ -726,7 +726,7 @@ defmodule Ash.Type do
|> case do
{terms, []} ->
if type.custom_apply_constraints_array?() do
case type.apply_constraints_array(Enum.reverse(term), constraints) do
case type.apply_constraints_array(Enum.reverse(terms), constraints) do
:ok -> {:ok, term}
other -> other
end

View file

@ -202,7 +202,7 @@ defmodule Ash.Test.Changeset.EmbeddedResourceTest do
end
test "embedded resources can be created" do
assert %{profile: %Profile{}, tags: [%Tag{}, %Tag{}]} =
assert %{profile: %Profile{}, tags: [%Tag{name: "trainer"}, %Tag{name: "human"}]} =
Changeset.for_create(
Author,
:create,