diff --git a/lib/ash_phoenix/form/form.ex b/lib/ash_phoenix/form/form.ex index a2b553a..55740e9 100644 --- a/lib/ash_phoenix/form/form.ex +++ b/lib/ash_phoenix/form/form.ex @@ -149,6 +149,8 @@ defmodule AshPhoenix.Form do :id, :transform_errors, :original_data, + any_removed?: false, + added?: false, changed?: false, touched_forms: MapSet.new(), data_updates: [], @@ -1471,20 +1473,17 @@ defmodule AshPhoenix.Form do end defp changed?(form) do - is_changed?(form) || + form.any_removed? || + is_changed?(form) || Enum.any?(form.forms, fn {_key, forms} -> forms |> List.wrap() - |> Enum.any?(& &1.changed?) + |> Enum.any?(&(&1.changed? || &1.added?)) end) end defp is_changed?(form) do - if form.type == :create do - true - else - attributes_changed?(form) || arguments_changed?(form) - end + attributes_changed?(form) || arguments_changed?(form) end defp attributes_changed?(%{source: %Ash.Query{}}), do: false @@ -1644,6 +1643,15 @@ defmodule AshPhoenix.Form do path: Enum.reverse(trail) end + found_form = form.forms[key] + + any_removed? = + if found_form && !found_form.added? do + true + else + false + end + new_config = form.form_keys |> Keyword.update!(key, fn config -> @@ -1659,6 +1667,7 @@ defmodule AshPhoenix.Form do %{ form | forms: new_forms, + any_removed?: form.any_removed? || any_removed?, form_keys: new_config, opts: Keyword.put(form.opts, :forms, new_config) } @@ -1674,11 +1683,21 @@ defmodule AshPhoenix.Form do new_config = do_remove_data(form, key, i) + found_form = Enum.at(form.forms[key] || [], i) + + any_removed? = + if found_form && !found_form.added? do + true + else + false + end + new_forms = form.forms |> Map.put_new(key, []) |> Map.update!(key, fn forms -> forms + |> Kernel.||([]) |> List.delete_at(i) |> Enum.with_index() |> Enum.map(fn {nested_form, i} -> @@ -1689,6 +1708,7 @@ defmodule AshPhoenix.Form do %{ form | forms: new_forms, + any_removed?: form.any_removed? || any_removed?, form_keys: new_config, opts: Keyword.put(form.opts, :forms, new_config) } @@ -1803,15 +1823,15 @@ defmodule AshPhoenix.Form do case config[:type] || :single do :single -> - %{new_form | name: form.name <> "[#{key}]", id: form.id <> "_#{key}"} + %{new_form | name: form.name <> "[#{key}]", id: form.id <> "_#{key}", added?: true} :list -> forms = List.wrap(forms) if opts[:prepend] do - [new_form | forms] + [%{new_form | added?: true} | forms] else - forms ++ [new_form] + forms ++ [%{new_form | added?: true}] end |> Enum.with_index() |> Enum.map(fn {nested_form, index} -> diff --git a/test/form_test.exs b/test/form_test.exs index 55d1691..0b0be06 100644 --- a/test/form_test.exs +++ b/test/form_test.exs @@ -24,13 +24,13 @@ defmodule AshPhoenix.FormTest do end describe "the .changed? field is updated as data changes" do - test "it is true for a create form with no changes" do + test "it is false for a create form with no changes" do form = Post |> Form.for_create(:create) |> Form.validate(%{}) - assert form.changed? + refute form.changed? end test "it is false by default for update forms" do @@ -80,6 +80,104 @@ defmodule AshPhoenix.FormTest do refute form.changed? end + + test "adding a form causes changed? to be true on the root form, but not the nested form" do + form = + Comment + |> Form.for_create(:create, + api: Api, + forms: [ + post: [ + resource: Post, + create_action: :create + ] + ] + ) + |> Form.add_form(:post, params: %{}) + + assert form.changed? + refute form.forms[:post].changed? + end + + test "removing a form that was there prior marks the form as changed" do + post = + Post + |> Ash.Changeset.new(%{text: "post"}) + |> Api.create!() + + comment = + Comment + |> Ash.Changeset.new(%{text: "comment"}) + |> Ash.Changeset.replace_relationship(:post, post) + |> Api.create!() + + # Check the persisted post.comments count after create + post = Post |> Api.get!(post.id) |> Api.load!(:comments) + assert Enum.count(post.comments) == 1 + + form = + post + |> Form.for_update(:update, + api: Api, + forms: [ + comments: [ + resource: Comment, + type: :list, + data: [comment], + create_action: :create, + update_action: :update + ] + ] + ) + + refute form.changed? + + form = Form.remove_form(form, [:comments, 0]) + + assert form.changed? + end + + test "removing a form that was added does not mark the form as changed" do + post = + Post + |> Ash.Changeset.new(%{text: "post"}) + |> Api.create!() + + comment = + Comment + |> Ash.Changeset.new(%{text: "comment"}) + |> Ash.Changeset.replace_relationship(:post, post) + |> Api.create!() + + # Check the persisted post.comments count after create + post = Post |> Api.get!(post.id) |> Api.load!(:comments) + assert Enum.count(post.comments) == 1 + + form = + post + |> Form.for_update(:update, + api: Api, + forms: [ + comments: [ + resource: Comment, + type: :list, + data: [comment], + create_action: :create, + update_action: :update + ] + ] + ) + + refute form.changed? + + form = Form.add_form(form, [:comments]) + + assert form.changed? + + form = Form.remove_form(form, [:comments, 1]) + + refute form.changed? + end end describe "errors" do