defmodule EctoMerge do @moduledoc """ This module holds helper funcions for recursively merging ecto records ands it's associations. """ import __MODULE__.Validator, only: [validate_mergeable!: 1] import __MODULE__.AssociationMerger, only: [merge_assocs: 4] import Ecto.Query, only: [from: 2] alias __MODULE__.Utils alias Ecto.{Changeset, Multi} @doc "Merges a list of records and it's associations recursively" def merge!(repo, records) do validate_mergeable!(records) schema = get_schema(records) ids = Enum.map(records, &(&1.id)) changeset = merge_into_changeset(repo, records) {:ok, %{result: result}} = Multi.new |> Multi.insert(:result, changeset) |> Multi.delete_all(:deleted, from(r in schema, where: r.id in ^ids)) |> repo.transaction result end defp merge_into_changeset(repo, records) do schema = get_schema(records) assocs = schema.__schema__(:associations) assocs_info = Enum.map(assocs, &schema.__schema__(:association, &1)) base_changeset = schema |> struct() |> schema.changeset(%{}) Enum.reduce(records, base_changeset, &( &2 |> merge_plain_attrs(&1) |> merge_assocs(&1, assocs_info, repo) )) end defp merge_plain_attrs(changeset, record) do plain_attrs = record |> Map.from_struct |> Map.delete(:id) |> Utils.cleanup_changes Changeset.change(changeset, plain_attrs) end defp get_schema([head | _tail]), do: get_schema(head) defp get_schema(%{__struct__: schema}), do: schema end