Skip to content

Commit cc6980a

Browse files
committed
handle NotLoaded structs when manipulating lists of not persisted data
1 parent 7a9f3c3 commit cc6980a

File tree

6 files changed

+111
-26
lines changed

6 files changed

+111
-26
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
## Unreleased
44

5+
## [0.1.2] - 2021-08-29
6+
7+
### Fixed
8+
9+
- Handle `Ecto.Association.NotLoaded` structs when appending, prepending or
10+
inserting data into relations that are child relations of newly added, not
11+
persisted data.
12+
513
## [0.1.1] - 2021-08-28
614

715
### Changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Add `ecto_nested_changeset` to your list of dependencies in `mix.exs`:
1111
```elixir
1212
def deps do
1313
[
14-
{:ecto_nested_changeset, "~> 0.1.1"}
14+
{:ecto_nested_changeset, "~> 0.1.2"}
1515
]
1616
end
1717
```

example/lib/nested_web/live/owner_live/form_component.ex

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ defmodule NestedWeb.OwnerLive.FormComponent do
1717

1818
@impl true
1919
def handle_event("validate", %{"owner" => owner_params}, socket) do
20-
owner_params = prepare_params(owner_params)
21-
2220
changeset =
2321
socket.assigns.owner
2422
|> Members.change_owner(owner_params)
@@ -110,18 +108,6 @@ defmodule NestedWeb.OwnerLive.FormComponent do
110108
end
111109
end
112110

113-
# puts empty lists into association fields if no associations were added
114-
defp prepare_params(owner_params) do
115-
owner_params
116-
|> Map.put_new("pets", [])
117-
|> Map.update!(
118-
"pets",
119-
&Enum.into(&1, %{}, fn {key, pet} ->
120-
{key, Map.put_new(pet, "toys", [])}
121-
end)
122-
)
123-
end
124-
125111
defp deleted?(form) do
126112
input_value(form, :delete) in ["true", true]
127113
end

lib/ecto_nested_changeset.ex

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ defmodule EctoNestedChangeset do
88

99
import Ecto.Changeset
1010

11+
alias Ecto.Association.NotLoaded
1112
alias Ecto.Changeset
1213

1314
@doc """
@@ -215,11 +216,13 @@ defmodule EctoNestedChangeset do
215216

216217
defp nested_update(:append, %Changeset{} = changeset, [field], value)
217218
when is_atom(field) do
218-
Changeset.put_change(
219-
changeset,
220-
field,
221-
get_change_or_field(changeset, field) ++ [value]
222-
)
219+
new_value =
220+
case {get_change_or_field(changeset, field), changeset.action} do
221+
{%NotLoaded{}, :insert} -> [value]
222+
{previous_value, _} -> previous_value ++ [value]
223+
end
224+
225+
Changeset.put_change(changeset, field, new_value)
223226
end
224227

225228
defp nested_update(:append, %{} = data, [field], value) when is_atom(field) do
@@ -230,11 +233,13 @@ defmodule EctoNestedChangeset do
230233

231234
defp nested_update(:prepend, %Changeset{} = changeset, [field], value)
232235
when is_atom(field) do
233-
Changeset.put_change(
234-
changeset,
235-
field,
236-
[value | get_change_or_field(changeset, field)]
237-
)
236+
new_value =
237+
case {get_change_or_field(changeset, field), changeset.action} do
238+
{%NotLoaded{}, :insert} -> [value]
239+
{previous_value, _} -> [value | previous_value]
240+
end
241+
242+
Changeset.put_change(changeset, field, new_value)
238243
end
239244

240245
defp nested_update(:prepend, %{} = data, [field], value)
@@ -249,6 +254,17 @@ defmodule EctoNestedChangeset do
249254
List.insert_at(items, index, value)
250255
end
251256

257+
defp nested_update(:insert, %Changeset{} = changeset, [field, index], value)
258+
when is_atom(field) and is_integer(index) do
259+
new_value =
260+
case {get_change_or_field(changeset, field), changeset.action} do
261+
{%NotLoaded{}, :insert} -> [value]
262+
{previous_value, _} -> List.insert_at(previous_value, index, value)
263+
end
264+
265+
Changeset.put_change(changeset, field, new_value)
266+
end
267+
252268
defp nested_update(:update, %Changeset{} = changeset, [field], func)
253269
when is_atom(field) do
254270
value = get_change_or_field(changeset, field)

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule EctoNestedChangeset.MixProject do
22
use Mix.Project
33

4-
@version "0.1.1"
4+
@version "0.1.2"
55
@source_url "https://github.com/woylie/ecto_nested_changeset"
66

77
def project do

test/ecto_nested_changeset_test.exs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,31 @@ defmodule EctoNestedChangesetTest do
5353
} = changeset.changes
5454
end
5555

56+
test "appends item at a sub field of a new list item" do
57+
changeset =
58+
%Category{id: 1, posts: []}
59+
|> change()
60+
|> append_at(:posts, %Post{title: "first"})
61+
|> append_at([:posts, 0, :comments], %Comment{})
62+
63+
assert %{
64+
posts: [
65+
%Ecto.Changeset{
66+
action: :insert,
67+
changes: %{
68+
comments: [
69+
%Ecto.Changeset{
70+
action: :insert,
71+
data: %Comment{}
72+
}
73+
]
74+
},
75+
data: %Post{title: "first"}
76+
}
77+
]
78+
} = changeset.changes
79+
end
80+
5681
test "appends item at a root level field with existing data" do
5782
changeset =
5883
%Category{id: 1, posts: [%Post{id: 1, title: "existing"}]}
@@ -164,6 +189,31 @@ defmodule EctoNestedChangesetTest do
164189
} = changeset.changes
165190
end
166191

192+
test "prepends item at a sub field of a new list item" do
193+
changeset =
194+
%Category{id: 1, posts: []}
195+
|> change()
196+
|> prepend_at(:posts, %Post{title: "first"})
197+
|> prepend_at([:posts, 0, :comments], %Comment{})
198+
199+
assert %{
200+
posts: [
201+
%Ecto.Changeset{
202+
action: :insert,
203+
changes: %{
204+
comments: [
205+
%Ecto.Changeset{
206+
action: :insert,
207+
data: %Comment{}
208+
}
209+
]
210+
},
211+
data: %Post{title: "first"}
212+
}
213+
]
214+
} = changeset.changes
215+
end
216+
167217
test "prepend item at a root level field with existing data" do
168218
changeset =
169219
%Category{id: 1, posts: [%Post{id: 1, title: "existing"}]}
@@ -283,6 +333,31 @@ defmodule EctoNestedChangesetTest do
283333
} = changeset.changes
284334
end
285335

336+
test "inserts item at a sub field of a new list item" do
337+
changeset =
338+
%Category{id: 1, posts: []}
339+
|> change()
340+
|> insert_at([:posts, 0], %Post{title: "first"})
341+
|> insert_at([:posts, 0, :comments, 0], %Comment{})
342+
343+
assert %{
344+
posts: [
345+
%Ecto.Changeset{
346+
action: :insert,
347+
changes: %{
348+
comments: [
349+
%Ecto.Changeset{
350+
action: :insert,
351+
data: %Comment{}
352+
}
353+
]
354+
},
355+
data: %Post{title: "first"}
356+
}
357+
]
358+
} = changeset.changes
359+
end
360+
286361
test "inserts item at a root level field with existing data" do
287362
changeset =
288363
%Category{

0 commit comments

Comments
 (0)