Skip to content

Commit 02d56cb

Browse files
committed
improve handling of relation fields that are not preloaded
1 parent eddbcc6 commit 02d56cb

File tree

6 files changed

+98
-11
lines changed

6 files changed

+98
-11
lines changed

CHANGELOG.md

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

33
## Unreleased
44

5+
## [0.1.3] - 2021-08-30
6+
7+
### Changed
8+
9+
- Raise `EctoNestedChangeset.NotLoadedError` in case the relation field of a
10+
loaded resource is not preloaded.
11+
- Handle list operations on root level relation fields if the field is not
12+
preloaded and the data is not persisted.
13+
514
## [0.1.2] - 2021-08-29
615

716
### Fixed

README.md

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

lib/ecto_nested_changeset.ex

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,14 @@ defmodule EctoNestedChangeset do
219219
defp nested_update(:append, %Changeset{} = changeset, [field], value)
220220
when is_atom(field) do
221221
new_value =
222-
case {get_change_or_field(changeset, field), changeset.action} do
223-
{%NotLoaded{}, :insert} -> [value]
224-
{previous_value, _} -> previous_value ++ [value]
222+
case get_change_or_field(changeset, field) do
223+
%NotLoaded{} ->
224+
if Ecto.get_meta(changeset.data, :state) == :built,
225+
do: [value],
226+
else: raise(EctoNestedChangeset.NotLoadedError, field: field)
227+
228+
previous_value ->
229+
previous_value ++ [value]
225230
end
226231

227232
Changeset.put_change(changeset, field, new_value)
@@ -236,9 +241,14 @@ defmodule EctoNestedChangeset do
236241
defp nested_update(:prepend, %Changeset{} = changeset, [field], value)
237242
when is_atom(field) do
238243
new_value =
239-
case {get_change_or_field(changeset, field), changeset.action} do
240-
{%NotLoaded{}, :insert} -> [value]
241-
{previous_value, _} -> [value | previous_value]
244+
case get_change_or_field(changeset, field) do
245+
%NotLoaded{} ->
246+
if Ecto.get_meta(changeset.data, :state) == :built,
247+
do: [value],
248+
else: raise(EctoNestedChangeset.NotLoadedError, field: field)
249+
250+
previous_value ->
251+
[value | previous_value]
242252
end
243253

244254
Changeset.put_change(changeset, field, new_value)
@@ -259,9 +269,14 @@ defmodule EctoNestedChangeset do
259269
defp nested_update(:insert, %Changeset{} = changeset, [field, index], value)
260270
when is_atom(field) and is_integer(index) do
261271
new_value =
262-
case {get_change_or_field(changeset, field), changeset.action} do
263-
{%NotLoaded{}, :insert} -> [value]
264-
{previous_value, _} -> List.insert_at(previous_value, index, value)
272+
case get_change_or_field(changeset, field) do
273+
%NotLoaded{} ->
274+
if Ecto.get_meta(changeset.data, :state) == :built,
275+
do: [value],
276+
else: raise(EctoNestedChangeset.NotLoadedError, field: field)
277+
278+
previous_value ->
279+
List.insert_at(previous_value, index, value)
265280
end
266281

267282
Changeset.put_change(changeset, field, new_value)

lib/exceptions.ex

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
defmodule EctoNestedChangeset.NotLoadedError do
2+
@moduledoc """
3+
Raised when a relation field that is updated is not preloaded.
4+
"""
5+
defexception [:field, :message]
6+
7+
def exception(opts) do
8+
field = Keyword.fetch!(opts, :field)
9+
message = "field `#{inspect(field)}` is not loaded"
10+
%__MODULE__{field: field, message: message}
11+
end
12+
end

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.2"
4+
@version "0.1.3"
55
@source_url "https://github.com/woylie/ecto_nested_changeset"
66

77
def project do

test/ecto_nested_changeset_test.exs

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

56+
test "doesn't raise error if field of unpersisted resource is not loaded" do
57+
%Category{id: 1}
58+
|> change()
59+
|> append_at(:posts, %Post{title: "first"})
60+
end
61+
62+
test "raises error if field of persisted resource is not preloaded" do
63+
assert_raise EctoNestedChangeset.NotLoadedError,
64+
"field `:posts` is not loaded",
65+
fn ->
66+
%Category{id: 1}
67+
|> Map.update!(:__meta__, &Map.put(&1, :state, :loaded))
68+
|> change()
69+
|> append_at(:posts, %Post{title: "first"})
70+
end
71+
end
72+
5673
test "appends item at a sub field of a new list item" do
5774
changeset =
5875
%Category{id: 1, posts: []}
@@ -189,6 +206,23 @@ defmodule EctoNestedChangesetTest do
189206
} = changeset.changes
190207
end
191208

209+
test "doesn't raise error if field of unpersisted resource is not loaded" do
210+
%Category{id: 1}
211+
|> change()
212+
|> prepend_at(:posts, %Post{title: "first"})
213+
end
214+
215+
test "raises error if field of persisted resource is not preloaded" do
216+
assert_raise EctoNestedChangeset.NotLoadedError,
217+
"field `:posts` is not loaded",
218+
fn ->
219+
%Category{id: 1}
220+
|> Map.update!(:__meta__, &Map.put(&1, :state, :loaded))
221+
|> change()
222+
|> prepend_at(:posts, %Post{title: "first"})
223+
end
224+
end
225+
192226
test "prepends item at a sub field of a new list item" do
193227
changeset =
194228
%Category{id: 1, posts: []}
@@ -333,6 +367,23 @@ defmodule EctoNestedChangesetTest do
333367
} = changeset.changes
334368
end
335369

370+
test "doesn't raise error if field of unpersisted resource is not loaded" do
371+
%Category{id: 1}
372+
|> change()
373+
|> insert_at([:posts, 0], %Post{title: "first"})
374+
end
375+
376+
test "raises error if field of persisted resource is not preloaded" do
377+
assert_raise EctoNestedChangeset.NotLoadedError,
378+
"field `:posts` is not loaded",
379+
fn ->
380+
%Category{id: 1}
381+
|> Map.update!(:__meta__, &Map.put(&1, :state, :loaded))
382+
|> change()
383+
|> insert_at([:posts, 0], %Post{title: "first"})
384+
end
385+
end
386+
336387
test "inserts item at a sub field of a new list item" do
337388
changeset =
338389
%Category{id: 1, posts: []}

0 commit comments

Comments
 (0)