Current section
Files
Jump to
Current section
Files
lib/services/store_adapter/ecto/mutation.ex
defmodule Sorcery.StoreAdapter.Ecto.Mutation do
@moduledoc false
alias Ecto.Multi, as: M
import Sorcery.Helpers.Maps
def run_mutation(inner_state, mutation) do
repo = inner_state.args.repo_module
schemas = inner_state.config_module.config().schemas
M.new()
|> handle_updates(mutation, schemas)
|> handle_inserts(mutation, schemas)
|> handle_deletes(mutation, schemas)
|> repo.transaction()
|> handle_formatting()
end
defp handle_formatting({:ok, resp}) do
default = %{updates: %{}, inserts: %{}, deletes: %{}}
m = Enum.reduce(resp, default, fn {transaction_id, entity_struct}, acc ->
entity = Map.from_struct(entity_struct)
|> Map.delete(:__meta__)
case String.split(transaction_id, ":") do
["insert", tk_str, _lvar] ->
tk = String.to_existing_atom(tk_str)
id = entity_struct.id
put_in_p(acc, [:inserts, tk, id], entity)
["update", tk_str, _id_str] ->
tk = String.to_existing_atom(tk_str)
id = entity_struct.id
put_in_p(acc, [:updates, tk, id], entity)
["delete", tk_str, id_str] ->
tk = String.to_existing_atom(tk_str)
id = String.to_integer(id_str)
update_in_p(acc, [:deletes, tk], [id], fn ids -> [id | ids] end)
end
end)
{:ok, m}
end
defp handle_formatting(err), do: err
# {{{ handle_updates
defp handle_updates(multi, mutation, schemas) do
Enum.reduce(mutation.updates, multi, fn {tk, table}, acc ->
mod = schemas[tk]
Enum.reduce(table, acc, fn {id, new_entity}, acc ->
original_entity = mutation.old_data[tk][id]
original_entity = struct(mod, original_entity)
cs = mod.sorcery_update_cs(original_entity, new_entity)
M.update(acc, "update:#{tk}:#{id}", cs)
end)
end)
end
# }}}
# {{{ handle_inserts
defp handle_inserts(multi, mutation, schemas) do
order = insert_order(mutation.inserts)
Enum.reduce(order, multi, fn {tk, lvar}, acc ->
mod = schemas[tk]
entity = get_in_p(mutation, [:inserts, tk, lvar])
id = entity.id
M.insert(acc, "insert:#{tk}:#{id}", fn m ->
entity = just_in_time_insertion_cleanup(m, entity, order)
mod.sorcery_insert_cs(entity)
end)
end)
end
defp just_in_time_insertion_cleanup(multi, entity, order) when is_struct(entity) do
just_in_time_insertion_cleanup(multi, Map.from_struct(entity), order)
end
defp just_in_time_insertion_cleanup(multi, entity, order) do
Enum.reduce(entity, entity, fn
{:id, _}, acc -> acc
{entity_attr, "?" <> _ = full_lvar}, acc ->
{lvar, attr} = String.split(full_lvar, ".")
|> case do
[lvar, attr] -> {lvar, String.to_existing_atom(attr)}
[lvar] -> {lvar, :id}
end
tk = Enum.find_value(order, fn {tk, l} -> if l == lvar, do: tk, else: nil end)
multi_k = "insert:#{tk}:#{lvar}"
new_v = get_in_p(multi, [multi_k, attr])
Map.put(acc, entity_attr, new_v)
_, acc -> acc
end)
end
@doc false
def insert_order(inserts) do
tups = Enum.reduce(inserts, [], fn {tk, table}, acc ->
lvars = Map.keys(table)
tk_tups = Enum.map(lvars, &{tk, &1})
[tk_tups | acc]
end) |> List.flatten()
insert_order(inserts, tups, [])
end
def insert_order(_inserts, [], ordered), do: ordered |> Enum.reverse()
def insert_order(inserts, [{tk, lvar} | tups], ordered) do
entity = get_in_p(inserts, [tk, lvar])
fields = entity |> Map.delete(:id) |> Map.values()
Enum.all?(fields, fn
"?" <> _ = dep ->
[dep | _] = String.split(dep, ".")
!Enum.any?(tups, fn {_, lvar} -> dep == lvar end)
_ -> true
end)
|> if do
insert_order(inserts, tups, [{tk, lvar} | ordered])
else
insert_order(inserts, tups ++ [{tk, lvar}], ordered)
end
end
# }}}
# {{{ handle_deletes
defp handle_deletes(multi, mutation, schemas) do
Enum.reduce(mutation.deletes, multi, fn {tk, ids}, acc ->
Enum.reduce(ids, acc, fn id, acc ->
M.delete(acc, "delete:#{tk}:#{id}", struct(schemas[tk], %{id: id}))
end)
end)
end
# }}}
end