Skip to content
Merged
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## Unreleased

## [0.1.3] - 2021-08-30

### Changed

- Raise `EctoNestedChangeset.NotLoadedError` in case the relation field of a
loaded resource is not preloaded.
- Handle list operations on root level relation fields if the field is not
preloaded and the data is not persisted.

## [0.1.2] - 2021-08-29

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Add `ecto_nested_changeset` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:ecto_nested_changeset, "~> 0.1.2"}
{:ecto_nested_changeset, "~> 0.1.3"}
]
end
```
Expand Down
33 changes: 24 additions & 9 deletions lib/ecto_nested_changeset.ex
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,14 @@ defmodule EctoNestedChangeset do
defp nested_update(:append, %Changeset{} = changeset, [field], value)
when is_atom(field) do
new_value =
case {get_change_or_field(changeset, field), changeset.action} do
{%NotLoaded{}, :insert} -> [value]
{previous_value, _} -> previous_value ++ [value]
case get_change_or_field(changeset, field) do
%NotLoaded{} ->
if Ecto.get_meta(changeset.data, :state) == :built,
do: [value],
else: raise(EctoNestedChangeset.NotLoadedError, field: field)

previous_value ->
previous_value ++ [value]
end

Changeset.put_change(changeset, field, new_value)
Expand All @@ -236,9 +241,14 @@ defmodule EctoNestedChangeset do
defp nested_update(:prepend, %Changeset{} = changeset, [field], value)
when is_atom(field) do
new_value =
case {get_change_or_field(changeset, field), changeset.action} do
{%NotLoaded{}, :insert} -> [value]
{previous_value, _} -> [value | previous_value]
case get_change_or_field(changeset, field) do
%NotLoaded{} ->
if Ecto.get_meta(changeset.data, :state) == :built,
do: [value],
else: raise(EctoNestedChangeset.NotLoadedError, field: field)

previous_value ->
[value | previous_value]
end

Changeset.put_change(changeset, field, new_value)
Expand All @@ -259,9 +269,14 @@ defmodule EctoNestedChangeset do
defp nested_update(:insert, %Changeset{} = changeset, [field, index], value)
when is_atom(field) and is_integer(index) do
new_value =
case {get_change_or_field(changeset, field), changeset.action} do
{%NotLoaded{}, :insert} -> [value]
{previous_value, _} -> List.insert_at(previous_value, index, value)
case get_change_or_field(changeset, field) do
%NotLoaded{} ->
if Ecto.get_meta(changeset.data, :state) == :built,
do: [value],
else: raise(EctoNestedChangeset.NotLoadedError, field: field)

previous_value ->
List.insert_at(previous_value, index, value)
end

Changeset.put_change(changeset, field, new_value)
Expand Down
12 changes: 12 additions & 0 deletions lib/exceptions.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule EctoNestedChangeset.NotLoadedError do
@moduledoc """
Raised when a relation field that is updated is not preloaded.
"""
defexception [:field, :message]

def exception(opts) do
field = Keyword.fetch!(opts, :field)
message = "field `#{inspect(field)}` is not loaded"
%__MODULE__{field: field, message: message}
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule EctoNestedChangeset.MixProject do
use Mix.Project

@version "0.1.2"
@version "0.1.3"
@source_url "https://github.com/woylie/ecto_nested_changeset"

def project do
Expand Down
51 changes: 51 additions & 0 deletions test/ecto_nested_changeset_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ defmodule EctoNestedChangesetTest do
} = changeset.changes
end

test "doesn't raise error if field of unpersisted resource is not loaded" do
%Category{id: 1}
|> change()
|> append_at(:posts, %Post{title: "first"})
end

test "raises error if field of persisted resource is not preloaded" do
assert_raise EctoNestedChangeset.NotLoadedError,
"field `:posts` is not loaded",
fn ->
%Category{id: 1}
|> Map.update!(:__meta__, &Map.put(&1, :state, :loaded))
|> change()
|> append_at(:posts, %Post{title: "first"})
end
end

test "appends item at a sub field of a new list item" do
changeset =
%Category{id: 1, posts: []}
Expand Down Expand Up @@ -189,6 +206,23 @@ defmodule EctoNestedChangesetTest do
} = changeset.changes
end

test "doesn't raise error if field of unpersisted resource is not loaded" do
%Category{id: 1}
|> change()
|> prepend_at(:posts, %Post{title: "first"})
end

test "raises error if field of persisted resource is not preloaded" do
assert_raise EctoNestedChangeset.NotLoadedError,
"field `:posts` is not loaded",
fn ->
%Category{id: 1}
|> Map.update!(:__meta__, &Map.put(&1, :state, :loaded))
|> change()
|> prepend_at(:posts, %Post{title: "first"})
end
end

test "prepends item at a sub field of a new list item" do
changeset =
%Category{id: 1, posts: []}
Expand Down Expand Up @@ -333,6 +367,23 @@ defmodule EctoNestedChangesetTest do
} = changeset.changes
end

test "doesn't raise error if field of unpersisted resource is not loaded" do
%Category{id: 1}
|> change()
|> insert_at([:posts, 0], %Post{title: "first"})
end

test "raises error if field of persisted resource is not preloaded" do
assert_raise EctoNestedChangeset.NotLoadedError,
"field `:posts` is not loaded",
fn ->
%Category{id: 1}
|> Map.update!(:__meta__, &Map.put(&1, :state, :loaded))
|> change()
|> insert_at([:posts, 0], %Post{title: "first"})
end
end

test "inserts item at a sub field of a new list item" do
changeset =
%Category{id: 1, posts: []}
Expand Down