diff --git a/lib/ash/actions/create.ex b/lib/ash/actions/create.ex index 531d64d3..bad65b98 100644 --- a/lib/ash/actions/create.ex +++ b/lib/ash/actions/create.ex @@ -133,18 +133,30 @@ defmodule Ash.Actions.Create do allowed_keys = resource |> Ash.attributes() - |> Enum.filter(& &1.writeable?) |> Enum.map(& &1.name) - attributes_with_defaults = + {attributes_with_defaults, unwriteable_attributes} = resource |> Ash.attributes() - |> Enum.filter(&(not is_nil(&1.default))) - |> Enum.reduce(attributes, fn attr, attributes -> - if has_attr?(attributes, attr.name) do - attributes - else - Map.put(attributes, attr.name, default(attr)) + |> Enum.reduce({%{}, []}, fn attribute, {new_attributes, unwriteable_attributes} -> + cond do + !attribute.writeable? && is_nil(attribute.default) -> + {new_attributes, unwriteable_attributes} + + !attribute.writeable? -> + {new_attributes, [attribute | unwriteable_attributes]} + + is_nil(attribute.default) -> + case fetch_attr(attributes, attribute.name) do + {:ok, value} -> + {Map.put(new_attributes, attribute.name, value), unwriteable_attributes} + + :error -> + {new_attributes, unwriteable_attributes} + end + + true -> + {Map.put(attributes, attribute.name, default(attribute)), unwriteable_attributes} end end) @@ -155,6 +167,13 @@ defmodule Ash.Actions.Create do |> Map.put(:action, :create) |> Map.put(:__ash_relationships__, %{}) + changeset = + Enum.reduce( + unwriteable_attributes, + changeset, + &Ecto.Changeset.add_error(&2, &1, "attribute is not writeable") + ) + resource |> Ash.attributes() |> Enum.reject(&Map.get(&1, :allow_nil?)) @@ -172,7 +191,13 @@ defmodule Ash.Actions.Create do defp default(%{default: {mod, func}}), do: apply(mod, func, []) defp default(%{default: function}), do: function.() - defp has_attr?(map, name) do - Map.has_key?(map, name) || Map.has_key?(map, to_string(name)) + defp fetch_attr(map, name) do + case Map.fetch(map, name) do + {:ok, value} -> + value + + :error -> + Map.fetch(map, to_string(name)) + end end end diff --git a/lib/ash/actions/update.ex b/lib/ash/actions/update.ex index 1ed9b637..00f12bc1 100644 --- a/lib/ash/actions/update.ex +++ b/lib/ash/actions/update.ex @@ -133,11 +133,40 @@ defmodule Ash.Actions.Update do |> Enum.filter(& &1.writeable?) |> Enum.map(& &1.name) + {attributes, unwriteable_attributes} = + resource + |> Ash.attributes() + |> Enum.reduce({%{}, []}, fn attribute, {new_attributes, unwriteable_attributes} -> + cond do + !attribute.writeable? && is_nil(attribute.default) -> + {new_attributes, unwriteable_attributes} + + !attribute.writeable? -> + {new_attributes, [attribute | unwriteable_attributes]} + + true -> + case fetch_attr(attributes, attribute.name) do + {:ok, value} -> + {Map.put(new_attributes, attribute.name, value), unwriteable_attributes} + + :error -> + {new_attributes, unwriteable_attributes} + end + end + end) + changeset = record |> Ecto.Changeset.cast(attributes, allowed_keys) |> Map.put(:action, :update) + changeset = + Enum.reduce( + unwriteable_attributes, + changeset, + &Ecto.Changeset.add_error(&2, &1, "attribute is not writeable") + ) + resource |> Ash.attributes() |> Enum.reject(&Map.get(&1, :allow_nil?)) @@ -151,4 +180,14 @@ defmodule Ash.Actions.Update do end end) end + + defp fetch_attr(map, name) do + case Map.fetch(map, name) do + {:ok, value} -> + value + + :error -> + Map.fetch(map, to_string(name)) + end + end end