Skip to content

Instantly share code, notes, and snippets.

@Doerge
Created June 10, 2020 17:57
Show Gist options
  • Save Doerge/f9278eec405f05e71443380878ea5e5f to your computer and use it in GitHub Desktop.
Save Doerge/f9278eec405f05e71443380878ea5e5f to your computer and use it in GitHub Desktop.
Example of copying a hierachy of an Ecto schema.
defmodule MyApp.Item do
use Ecto.Schema
import Ecto.Changeset
alias MyApp.Item
schema "items" do
field :name, :string
belongs_to :parent, Item
has_many :children, Item, foreign_key: :parent_id
timestamps()
end
def copy_changeset(item, attrs \\ %{}) do
%Item{
name: item.name,
parent_id: item.parent_id,
children: []
}
|> cast(attrs, [
:name
])
end
end
defmodule MyApp.MyContext do
alias Ecto.Multi
alias MyApp.Repo
alias MyApp.Item
@doc """
Inserts an item copy in the Multi-arg.
## Examples
iex> copy_item(item, 42, Multi.new())
%{:ok, %{{:item, 42} => new_item}
"""
def copy_item(item, item_key, multi, attrs \\ %{}) do
new_item_changeset = Item.copy_changeset(item, attrs)
multi
|> Multi.insert({:item, item_key}, new_item_changeset)
end
@doc """
Deep copies the item hierachy.
## Examples
iex> copy_item_hierachy(item, Multi.new()) |> Repo.transaction()
%{:ok, %{{:item, :root} => new_item, {:item, 0} => new_item_child0, ...}
Changes are cast in the item's copy_changeset functions, so it's possible to
inject changes on the fly:
iex> copy_item_hierachy(item, Multi.new(), %{name: "parent"}, %{name: "child"}) |> Repo.transaction()
%{:ok, %{{:item, :root} => %Item{name: "parent"}, {:item, 0} => %Item{name: "child"}, ...}
"""
def copy_item_hierachy(item, multi, root_attrs \\ %{}, child_attrs \\ %{}) do
# Copy the item
root_item_multi = copy_item(item, :root, multi, root_attrs)
# Handle children
root_item_multi
|> Multi.merge(fn %{{:item, :root} => new_root_item} ->
item.children
|> Enum.with_index()
|> Enum.reduce(Multi.new(), fn {child_item, item_index}, acc_multi ->
attrs = Map.put(child_attrs, :parent_id, new_root_item.id)
copy_item(child_item, item_index, acc_multi, attrs)
end)
end)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment