Current section
Files
Jump to
Current section
Files
lib/ecto_merge.ex
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]
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)
{changeset, records_to_delete} =
merge_into_changeset(repo, records)
{:ok, %{result: result}} =
Multi.new
|> Multi.insert(:result, changeset)
|> delete(records_to_delete ++ records)
|> 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, []},
fn record, {acc_changeset, acc_records_to_delete} ->
{changeset, records_to_delete} =
acc_changeset
|> merge_plain_attrs(record)
|> merge_assocs(record, assocs_info, repo)
{changeset, acc_records_to_delete ++ records_to_delete}
end
)
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
defp delete(%Ecto.Multi{} = multi, records_to_delete) do
records_to_delete
|> Enum.with_index
|> Enum.reduce(multi, fn({record, i}, acc_multi) ->
Multi.delete(acc_multi, :"delete_extra_record_#{i}", record)
end)
end
end