EctoNestedChangeset (Ecto Nested Changeset v0.1.1) View Source

This module defines function for manipulating nested changesets.

All functions take a path as the second argument. The path is a list of atoms (for field names) and integers (for indexes in lists).

Link to this section Summary

Functions

Appends a value to the field referenced by the path.

Deletes the item at the given path.

Inserts a value into a field at the given position.

Prepends a value to the field referenced by the path.

Updates the value in the changeset at the given position with the given update function.

Link to this section Functions

Link to this function

append_at(changeset, path, value)

View Source

Specs

append_at(Ecto.Changeset.t(), [atom() | non_neg_integer()] | atom(), any()) ::
  Ecto.Changeset.t()

Appends a value to the field referenced by the path.

The last path segment must be an atom referencing either a to-many relation field or an array field.

Example

iex> %Owner{pets: [%Pet{}, %Pet{toys: [%Toy{name: "stick"}]}]}
...> |> Ecto.Changeset.change()
...> |> append_at(changeset, [:pets, 1, :toys], %Toy{name: "ball"})
...> |> Ecto.Changeset.apply_changes()
%Owner{
  pets: [
    %Pet{},
    %Pet{toys: [%Toy{name: "stick"}, %Toy{name: "ball"}]}
  ]
}
Link to this function

delete_at(changeset, path, opts \\ [])

View Source

Specs

Deletes the item at the given path.

The last path segment is expected to be an integer index.

Items that are not persisted in the database yet will always be removed from the list. For structs that are already persisted in the database, there are three different modes.

  • [mode: {:action, :replace}] (default) - The item will be wrapped in a changeset with the :replace action. This only works if an appropriate :on_replace option is set for the relation in the schema.
  • [mode: {:action, :delete}] - The item will be wrapped in a changeset with the action set to :delete.
  • [mode: {:flag, field}] - Puts true as a change for the given field.

The flag option useful for explicitly marking items for deletion in form parameters. In this case, you would configure a virtual field on the schema and set the changeset action to :delete in the changeset function in case the value is set to true.

schema "pets" do
  field :name, :string
  field :delete, :boolean, virtual: true, default: false
end

def changeset(pet, attrs) do
  pet
  |> cast(attrs, [:name, :delete])
  |> validate_required([:name])
  |> maybe_mark_for_deletion()
end

def maybe_mark_for_deletion(%Ecto.Changeset{} = changeset) do
  if Ecto.Changeset.get_change(changeset, :delete),
    do: Map.put(changeset, :action, :delete),
    else: changeset
end

Examples

iex> changeset = Ecto.Changeset.change(
       %Owner{pets: [%Pet{name: "George"}, %Pet{name: "Patty"}]}
...> )
iex> delete_at(changeset, [:pets, 1])
%Ecto.Changeset{
  changes: [
    %Changeset{action: :replace, data: %Post{name: "Patty"}},
    %Changeset{action: :update, data: %Post{name: "George"}},
  ]
}
iex> delete_at(changeset, [:pets, 1], mode: {:action, :delete})
%Ecto.Changeset{
  changes: [
    %Changeset{action: :update, data: %Post{name: "George"}},
    %Changeset{action: :delete, data: %Post{name: "Patty"}},
  ]
}
iex> delete_at(changeset, [:pets, 1], mode: {:field, :delete})
%Ecto.Changeset{
  changes: [
    %Changeset{action: :update, data: %Post{name: "George"}},
    %Changeset{
      action: :update,
      changes: %{delete: true},
      data: %Post{name: "Patty"}
    },
  ]
}
Link to this function

insert_at(changeset, path, value)

View Source

Specs

insert_at(Ecto.Changeset.t(), [atom() | non_neg_integer()] | atom(), any()) ::
  Ecto.Changeset.t()

Inserts a value into a field at the given position.

The last path segment must be an integer for the position.

Example

iex> %Owner{
...>   pets: [
...>     %Pet{},
...>     %Pet{toys: [%Toy{name: "stick"}, %Toy{name: "ball"}]}
...>   ]
...> }
...> |> Ecto.Changeset.change()
...> |> insert_at(changeset, [:pets, 1, :toys, 1], %Toy{name: "rope"})
...> |> Ecto.Changeset.apply_changes()
%Owner{
  pets: [
    %Pet{},
    %Pet{
      toys: [
        %Toy{name: "ball"},
        %Toy{name: "rope"},
        %Toy{name: "stick"}
      ]
    }
  ]
}
Link to this function

prepend_at(changeset, path, value)

View Source

Specs

prepend_at(Ecto.Changeset.t(), [atom() | non_neg_integer()] | atom(), any()) ::
  Ecto.Changeset.t()

Prepends a value to the field referenced by the path.

The last path segment must be an atom referencing either a to-many relation field or an array field.

Example

iex> %Owner{pets: [%Pet{}, %Pet{toys: [%Toy{name: "stick"}]}]}
...> |> Ecto.Changeset.change()
...> |> prepend_at(changeset, [:pets, 1, :toys], %Toy{name: "ball"})
...> |> Ecto.Changeset.apply_changes()
%Owner{
  pets: [
    %Pet{},
    %Pet{toys: [%Toy{name: "ball"}, %Toy{name: "stick"}]}
  ]
}
Link to this function

update_at(changeset, path, func)

View Source

Specs

update_at(
  Ecto.Changeset.t(),
  [atom() | non_neg_integer()] | atom(),
  (any() -> any())
) :: Ecto.Changeset.t()

Updates the value in the changeset at the given position with the given update function.

The path may lead to any field, including arrays and relation fields. Unlike Ecto.Changeset.update_change/3, the update function is always applied, either to the change or to existing value. The values will not be unwrapped, which means that the update function passed as the last parameter must potentially handle either changesets or raw values, depending on the path.

Examples

iex> %Owner{pets: [%Pet{toys: [%Toy{name: "stick"}, %Toy{name: "ball"}]}]}
...> |> Ecto.Changeset.change()
...> |> update_at(
...>      changeset,
...>      [:pets, 1, :toys, 1, :name],
...>      &String.upcase/1
...>    )
...> |> Ecto.Changeset.apply_changes()
%Owner{
  pets: [
    %Pet{},
    %Pet{
      toys: [
        %Toy{name: "stick"},
        %Toy{name: "BALL"}
      ]
    }
  ]
}