diff --git a/lib/ash/actions/managed_relationships.ex b/lib/ash/actions/managed_relationships.ex index 55dd505f..9a5607a6 100644 --- a/lib/ash/actions/managed_relationships.ex +++ b/lib/ash/actions/managed_relationships.ex @@ -5,18 +5,35 @@ defmodule Ash.Actions.ManagedRelationships do alias Ash.Error.Changes.InvalidRelationship alias Ash.Error.Query.NotFound - def load(_api, created, %{relationships: rels}, _) when rels == %{}, do: {:ok, created} + def load(_api, created, %{relationships: rels}, _) when rels == %{}, + do: {:ok, created} + def load(_api, created, %{relationships: nil}, _), do: {:ok, created} - def load(api, created, changeset, opts) do - if Ash.Changeset.ManagedRelationshipHelpers.must_load?(opts) do - api.load(created, Map.keys(changeset.relationships), - authorize?: opts[:authorize?], - actor: opts[:actor] - ) - else - {:ok, created} - end + def load(api, created, changeset, engine_opts) do + Enum.reduce_while(changeset.relationships, {:ok, created}, fn {key, value}, {:ok, acc} -> + relationship = Ash.Resource.Info.relationship(changeset.resource, key) + + case Enum.filter(value, fn {_, opts} -> + opts = Ash.Changeset.ManagedRelationshipHelpers.sanitize_opts(relationship, opts) + Ash.Changeset.ManagedRelationshipHelpers.must_load?(opts) + end) do + [] -> + {:cont, {:ok, acc}} + + relationships -> + authorize? = + engine_opts[:authorize?] && + Enum.any?(relationships, fn {_, opts} -> opts[:authorize?] end) + + actor = engine_opts[:actor] + + case api.load(acc, key, authorize?: authorize?, actor: actor) do + {:ok, loaded} -> {:cont, {:ok, loaded}} + {:error, error} -> {:halt, {:error, error}} + end + end + end) end def setup_managed_belongs_to_relationships(changeset, actor, engine_opts) do diff --git a/lib/ash/changeset/changeset.ex b/lib/ash/changeset/changeset.ex index 1fdf62d1..a92603cc 100644 --- a/lib/ash/changeset/changeset.ex +++ b/lib/ash/changeset/changeset.ex @@ -1718,7 +1718,8 @@ defmodule Ash.Changeset do add_invalid_errors(:attribute, changeset, attribute, "Attribute is not writable") attribute -> - with {:ok, prepared} <- + with value <- handle_indexed_maps(attribute.type, value), + {:ok, prepared} <- prepare_change(changeset, attribute, value, attribute.constraints), {:ok, casted} <- cast_input(attribute.type, prepared, attribute.constraints, true), @@ -1844,7 +1845,8 @@ defmodule Ash.Changeset do %{changeset | attributes: Map.put(changeset.attributes, attribute.name, nil)} attribute -> - with {:ok, prepared} <- + with value <- handle_indexed_maps(attribute.type, value), + {:ok, prepared} <- prepare_change(changeset, attribute, value, attribute.constraints), {:ok, casted} <- cast_input(attribute.type, prepared, attribute.constraints), diff --git a/lib/ash/embeddable_type.ex b/lib/ash/embeddable_type.ex index 5c2a245a..051cd118 100644 --- a/lib/ash/embeddable_type.ex +++ b/lib/ash/embeddable_type.ex @@ -128,7 +128,7 @@ defmodule Ash.EmbeddableType do defmacro single_embed_implementation do # credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks - quote do + quote location: :keep do alias __MODULE__.ShadowApi def storage_type, do: :map @@ -181,7 +181,7 @@ defmodule Ash.EmbeddableType do def fetch_key(map, atom) do case Map.fetch(map, atom) do {:ok, value} -> - value + {:ok, value} :error -> Map.fetch(map, to_string(atom)) @@ -321,7 +321,7 @@ defmodule Ash.EmbeddableType do :error -> {pkey_field, :error} - value -> + {:ok, value} -> attribute = Ash.Resource.Info.attribute(__MODULE__, pkey_field) case Ash.Type.cast_input(attribute.type, value, attribute.constraints) do @@ -362,7 +362,7 @@ defmodule Ash.EmbeddableType do defmacro array_embed_implementation do # credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks - quote do + quote location: :keep do alias __MODULE__.ShadowApi def array_constraints, do: Ash.EmbeddableType.embedded_resource_array_constraints() @@ -482,9 +482,9 @@ defmodule Ash.EmbeddableType do Enum.into(pkey_fields, %{}, fn pkey_field -> case fetch_key(new, pkey_field) do :error -> - :error + {pkey_field, :error} - value -> + {:ok, value} -> attr = Map.get(pkey_attributes, pkey_field) case Ash.Type.cast_input(attr.type, value, attr.constraints) do @@ -492,7 +492,7 @@ defmodule Ash.EmbeddableType do {pkey_field, casted} _ -> - :error + {pkey_field, :error} end end end) @@ -538,7 +538,7 @@ defmodule Ash.EmbeddableType do end defmacro define_embeddable_type do - quote do + quote location: :keep do use Ash.Type parent = __MODULE__