Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 51 additions & 26 deletions lib/polymorphic_embed/html/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,44 +49,68 @@ 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}]")

params = Map.get(source_changeset.params || %{}, to_string(field), %{}) |> List.wrap()

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

Expand All @@ -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
Expand Down
146 changes: 132 additions & 14 deletions test/polymorphic_embed_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 =
Expand All @@ -2732,7 +2754,7 @@ defmodule PolymorphicEmbedTest do
<input id="reminder_channel_address" name="reminder[channel][address]" type="text" value="a">
""",
else: ~s"""
<input id="reminder_channel_address" name="reminder[channel][address]" type="text" value="a">
<input id="reminder_channel_number" name="reminder[channel][number]" type="text" value="a">
"""
)

Expand All @@ -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
)

Expand All @@ -2756,7 +2783,7 @@ defmodule PolymorphicEmbedTest do
<input id="reminder_channel_address" name="reminder[channel][address]" type="text" value="a">
""",
else: ~s"""
<input id="reminder_channel_address" name="reminder[channel][address]" type="text" value="a">
<input id="reminder_channel_number" name="reminder[channel][number]" type="text" value="a">
"""
)

Expand Down Expand Up @@ -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(
Expand Down