diff --git a/lib/polymorphic_embed/html/helpers.ex b/lib/polymorphic_embed/html/helpers.ex index 7822d46..285f59f 100644 --- a/lib/polymorphic_embed/html/helpers.ex +++ b/lib/polymorphic_embed/html/helpers.ex @@ -49,7 +49,7 @@ if Code.ensure_loaded?(Phoenix.HTML) && Code.ensure_loaded?(Phoenix.HTML.Form) d form.source.data.__struct__ end - def to_form(%{action: parent_action} = source_changeset, form, field, options) do + def to_form(source_changeset, %{action: parent_action} = form, field, options) do id = to_string(form.id <> "_#{field}") name = to_string(form.name <> "[#{field}]") @@ -57,36 +57,60 @@ if Code.ensure_loaded?(Phoenix.HTML) && Code.ensure_loaded?(Phoenix.HTML.Form) d struct = Ecto.Changeset.apply_changes(source_changeset) - list_data = - case Map.get(struct, field) do - nil -> - type = Keyword.get(options, :polymorphic_type, get_polymorphic_type(form, field)) - module = PolymorphicEmbed.get_polymorphic_module(struct.__struct__, field, type) - if module, do: [struct(module)], else: [] + Map.get(source_changeset.changes, field) + |> case do + nil -> + case Map.get(struct, field) do + nil -> + type = Keyword.get(options, :polymorphic_type, get_polymorphic_type(form, field)) + module = PolymorphicEmbed.get_polymorphic_module(struct.__struct__, field, type) + if module, do: [struct(module)], else: [] - data -> - List.wrap(data) - end + data -> + List.wrap(data) + end - list_data + data when is_list(data) -> + data + + data -> + List.wrap(data) + end |> Enum.with_index() - |> Enum.map(fn {data, i} -> - params = Enum.at(params, i) || %{} + |> Enum.map(fn + {%Ecto.Changeset{} = changeset, i} -> + params = changeset.params || %{} + errors = get_errors(changeset) - changeset = - data - |> Ecto.Changeset.change() - |> apply_action(parent_action) + %{changeset: changeset, params: params, errors: errors, index: i} - errors = get_errors(changeset) + {data, i} -> + params = Enum.at(params, i) || %{} - changeset = %Ecto.Changeset{ - changeset - | action: parent_action, - params: params, - errors: errors, - valid?: errors == [] - } + changeset = + data + |> Ecto.Changeset.change() + |> apply_action(parent_action) + + errors = get_errors(changeset) + + changeset = %{ + changeset + | action: parent_action, + params: params, + errors: errors, + valid?: errors == [] + } + + %{changeset: changeset, params: params, errors: errors, index: i} + end) + |> Enum.map(fn prepared_data -> + %{ + changeset: changeset, + params: params, + errors: errors, + index: i + } = prepared_data %schema{} = source_changeset.data @@ -106,7 +130,8 @@ if Code.ensure_loaded?(Phoenix.HTML) && Code.ensure_loaded?(Phoenix.HTML.Form) d name: if(array?, do: name <> "[" <> index_string <> "]", else: name), index: if(array?, do: i), errors: errors, - data: data, + data: changeset.data, + action: parent_action, params: params, hidden: [{type_field_name, to_string(type)}], options: options diff --git a/test/polymorphic_embed_test.exs b/test/polymorphic_embed_test.exs index 7dced00..01d3d44 100644 --- a/test/polymorphic_embed_test.exs +++ b/test/polymorphic_embed_test.exs @@ -2704,15 +2704,26 @@ defmodule PolymorphicEmbedTest do for generator <- @generators do reminder_module = get_module(Reminder, generator) - attrs = %{ - date: ~U[2020-05-28 02:57:19Z], - text: "This is an Email reminder", - channel: %{ - address: "a", - valid: true, - confirmed: true - } - } + attrs = + if polymorphic?(generator) do + %{ + date: ~U[2020-05-28 02:57:19Z], + text: "This is an Email reminder", + channel: %{ + address: "a", + valid: true, + confirmed: true + } + } + else + %{ + number: ~U[2020-05-28 02:57:19Z], + text: "This is a non polymorphic reminder", + channel: %{ + number: "a" + } + } + end changeset = struct(reminder_module) @@ -2721,8 +2732,19 @@ defmodule PolymorphicEmbedTest do contents = safe_inputs_for(changeset, :channel, generator, fn f -> assert f.impl == Phoenix.HTML.FormData.Ecto.Changeset - assert f.errors == [] - text_input(f, :address) + + if polymorphic?(generator) do + assert f.errors == [ + {:address, + {"should be at least %{count} character(s)", + [count: 3, validation: :length, kind: :min, type: :string]}} + ] + + text_input(f, :address) + else + assert f.errors == [] + text_input(f, :number) + end end) expected_contents = @@ -2732,7 +2754,7 @@ defmodule PolymorphicEmbedTest do """, else: ~s""" - + """ ) @@ -2745,7 +2767,12 @@ defmodule PolymorphicEmbedTest do generator, fn f -> assert f.impl == Phoenix.HTML.FormData.Ecto.Changeset - text_input(f, :address) + + if polymorphic?(generator) do + text_input(f, :address) + else + text_input(f, :number) + end end ) @@ -2756,7 +2783,7 @@ defmodule PolymorphicEmbedTest do """, else: ~s""" - + """ ) @@ -3204,6 +3231,97 @@ defmodule PolymorphicEmbedTest do end) end + test "form with improved param handling for different param types" do + reminder_module = get_module(Reminder, :polymorphic) + + # Test with nil params - when contexts is empty, safe_inputs_for returns empty string + changeset_nil_params = + struct(reminder_module) + |> reminder_module.changeset(%{text: "Test reminder", contexts: []}) + |> Map.put(:params, %{"contexts" => nil}) + |> Map.put(:action, :insert) + + safe_form_for(changeset_nil_params, fn _f -> + safe_inputs_for(changeset_nil_params, :contexts, :polymorphic, fn f -> + assert f.impl == Phoenix.HTML.FormData.Ecto.Changeset + assert f.errors == [] + assert f.params == %{} + + 1 + end) + + 1 + end) + + # Test with list params - need to add some contexts to the data + changeset_list_params = + struct(reminder_module) + |> reminder_module.changeset(%{ + text: "Test reminder", + contexts: [ + %{__type__: "device", ref: "123", type: "cellphone"}, + %{__type__: "location", address: "456 Main St"} + ] + }) + |> Map.put(:params, %{"contexts" => [%{"ref" => "123"}, %{"address" => "456 Main St"}]}) + |> Map.put(:action, :insert) + + safe_form_for(changeset_list_params, fn _f -> + safe_inputs_for(changeset_list_params, :contexts, :polymorphic, fn f -> + assert f.impl == Phoenix.HTML.FormData.Ecto.Changeset + assert f.errors == [] + # First context should have params from index 0 + if f.index == 0 do + assert f.params == %{"ref" => "123"} + end + + # Second context should have params from index 1 + if f.index == 1 do + assert f.params == %{"address" => "456 Main St"} + end + + 1 + end) + + 1 + end) + + # Test with map params - need to add some contexts to the data + changeset_map_params = + struct(reminder_module) + |> reminder_module.changeset(%{ + text: "Test reminder", + contexts: [ + %{__type__: "device", ref: "789", type: "cellphone"}, + %{__type__: "location", address: "012 Oak Ave"} + ] + }) + |> Map.put(:params, %{ + "contexts" => %{"0" => %{"ref" => "789"}, "1" => %{"address" => "012 Oak Ave"}} + }) + |> Map.put(:action, :insert) + + safe_form_for(changeset_map_params, fn _f -> + safe_inputs_for(changeset_map_params, :contexts, :polymorphic, fn f -> + assert f.impl == Phoenix.HTML.FormData.Ecto.Changeset + assert f.errors == [] + # First context should have params from key "0" + if f.index == 0 do + assert f.params == %{"ref" => "789"} + end + + # Second context should have params from key "1" + if f.index == 1 do + assert f.params == %{"address" => "012 Oak Ave"} + end + + 1 + end) + + 1 + end) + end + describe "get_polymorphic_type/3" do test "returns the type for a module" do assert PolymorphicEmbed.get_polymorphic_type(