EctoNestedChangeset (Ecto Nested Changeset v1.0.0)
View SourceThis 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).
Summary
Functions
Appends a value to the field referenced by the path.
Deletes the item at the given path.
Returns a value from a changeset referenced by the 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.
Functions
@spec 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"}]}
]
}
@spec delete_at(Ecto.Changeset.t(), [atom() | non_neg_integer()] | atom(), keyword()) :: Ecto.Changeset.t()
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}]
- Putstrue
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"}
},
]
}
@spec get_at(Ecto.Changeset.t(), [atom() | non_neg_integer()] | atom()) :: any()
Returns a value from a changeset referenced by the path.
Example
iex> %Owner{pets: [%Pet{}, %Pet{toys: [%Toy{name: "stick"}]}]}
...> |> Ecto.Changeset.change()
...> |> get_at(changeset, [:pets, 1, :toys])
[%Toy{name: "stick"}, %Toy{name: "ball"}]
@spec 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"}
]
}
]
}
@spec 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"}]}
]
}
@spec 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.
If the path points to a field with a simple type, the update function will receive the raw value of the field. However, if the path points to the field of a *-to-many relation, the list values will not be unwrapped, which means that the update function has to handle a list of changesets.
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"}
]
}
]
}