Skip to content

Commit

Permalink
handle NotLoaded structs when manipulating lists of not persisted data
Browse files Browse the repository at this point in the history
  • Loading branch information
woylie committed Aug 28, 2021
1 parent 7a9f3c3 commit cc6980a
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 26 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased

## [0.1.2] - 2021-08-29

### Fixed

- Handle `Ecto.Association.NotLoaded` structs when appending, prepending or
inserting data into relations that are child relations of newly added, not
persisted data.

## [0.1.1] - 2021-08-28

### Changed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Add `ecto_nested_changeset` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:ecto_nested_changeset, "~> 0.1.1"}
{:ecto_nested_changeset, "~> 0.1.2"}
]
end
```
Expand Down
14 changes: 0 additions & 14 deletions example/lib/nested_web/live/owner_live/form_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ defmodule NestedWeb.OwnerLive.FormComponent do

@impl true
def handle_event("validate", %{"owner" => owner_params}, socket) do
owner_params = prepare_params(owner_params)

changeset =
socket.assigns.owner
|> Members.change_owner(owner_params)
Expand Down Expand Up @@ -110,18 +108,6 @@ defmodule NestedWeb.OwnerLive.FormComponent do
end
end

# puts empty lists into association fields if no associations were added
defp prepare_params(owner_params) do
owner_params
|> Map.put_new("pets", [])
|> Map.update!(
"pets",
&Enum.into(&1, %{}, fn {key, pet} ->
{key, Map.put_new(pet, "toys", [])}
end)
)
end

defp deleted?(form) do
input_value(form, :delete) in ["true", true]
end
Expand Down
36 changes: 26 additions & 10 deletions lib/ecto_nested_changeset.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule EctoNestedChangeset do

import Ecto.Changeset

alias Ecto.Association.NotLoaded
alias Ecto.Changeset

@doc """
Expand Down Expand Up @@ -215,11 +216,13 @@ defmodule EctoNestedChangeset do

defp nested_update(:append, %Changeset{} = changeset, [field], value)
when is_atom(field) do
Changeset.put_change(
changeset,
field,
get_change_or_field(changeset, field) ++ [value]
)
new_value =
case {get_change_or_field(changeset, field), changeset.action} do
{%NotLoaded{}, :insert} -> [value]
{previous_value, _} -> previous_value ++ [value]
end

Changeset.put_change(changeset, field, new_value)
end

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

defp nested_update(:prepend, %Changeset{} = changeset, [field], value)
when is_atom(field) do
Changeset.put_change(
changeset,
field,
[value | get_change_or_field(changeset, field)]
)
new_value =
case {get_change_or_field(changeset, field), changeset.action} do
{%NotLoaded{}, :insert} -> [value]
{previous_value, _} -> [value | previous_value]
end

Changeset.put_change(changeset, field, new_value)
end

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

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)
end

Changeset.put_change(changeset, field, new_value)
end

defp nested_update(:update, %Changeset{} = changeset, [field], func)
when is_atom(field) do
value = get_change_or_field(changeset, field)
Expand Down
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.1"
@version "0.1.2"
@source_url "https://github.com/woylie/ecto_nested_changeset"

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

test "appends item at a sub field of a new list item" do
changeset =
%Category{id: 1, posts: []}
|> change()
|> append_at(:posts, %Post{title: "first"})
|> append_at([:posts, 0, :comments], %Comment{})

assert %{
posts: [
%Ecto.Changeset{
action: :insert,
changes: %{
comments: [
%Ecto.Changeset{
action: :insert,
data: %Comment{}
}
]
},
data: %Post{title: "first"}
}
]
} = changeset.changes
end

test "appends item at a root level field with existing data" do
changeset =
%Category{id: 1, posts: [%Post{id: 1, title: "existing"}]}
Expand Down Expand Up @@ -164,6 +189,31 @@ defmodule EctoNestedChangesetTest do
} = changeset.changes
end

test "prepends item at a sub field of a new list item" do
changeset =
%Category{id: 1, posts: []}
|> change()
|> prepend_at(:posts, %Post{title: "first"})
|> prepend_at([:posts, 0, :comments], %Comment{})

assert %{
posts: [
%Ecto.Changeset{
action: :insert,
changes: %{
comments: [
%Ecto.Changeset{
action: :insert,
data: %Comment{}
}
]
},
data: %Post{title: "first"}
}
]
} = changeset.changes
end

test "prepend item at a root level field with existing data" do
changeset =
%Category{id: 1, posts: [%Post{id: 1, title: "existing"}]}
Expand Down Expand Up @@ -283,6 +333,31 @@ defmodule EctoNestedChangesetTest do
} = changeset.changes
end

test "inserts item at a sub field of a new list item" do
changeset =
%Category{id: 1, posts: []}
|> change()
|> insert_at([:posts, 0], %Post{title: "first"})
|> insert_at([:posts, 0, :comments, 0], %Comment{})

assert %{
posts: [
%Ecto.Changeset{
action: :insert,
changes: %{
comments: [
%Ecto.Changeset{
action: :insert,
data: %Comment{}
}
]
},
data: %Post{title: "first"}
}
]
} = changeset.changes
end

test "inserts item at a root level field with existing data" do
changeset =
%Category{
Expand Down

0 comments on commit cc6980a

Please sign in to comment.