defmodule Sorcery.Storage.EctoAdapter do use Norm alias Sorcery.Specs.Primative, as: T alias Sorcery.Storage.GenserverAdapter.Specs, as: AdapterT import Sorcery.Storage.Adapters.Ecto.Specs alias Sorcery.Storage.Adapters.Ecto.Parsing #alias Sorcery.Utils.Maps @contract persist_src(T.src(), AdapterT.client_state()) :: T.db() @doc """ Takes the src, and makes it permanent with an ecto transaction """ def persist_src(src, client) do %{repo: repo} = client multi = multi_mod(client).new() multi |> build_multi_inserts(src, client) |> build_multi_updates(src, client) |> build_multi_deletes(src, client) |> repo.transaction() |> case do {:ok, ops} -> Enum.reduce(ops, %{}, fn {name, entity}, acc -> tk_str = case String.split(name, ":") do [tk_str, _id_str] -> tk_str ["$sorcery", tk_str, _id_str] -> tk_str end tk = String.to_existing_atom(tk_str) acc |> Map.put_new(tk, %{}) |> put_in([tk, entity.id], Map.from_struct(entity)) end) error -> error end end defp build_multi_inserts(multi, src, client) do Parsing.get_ordered_inserts(src) |> Enum.reduce(multi, fn {tk, id, entity}, multi -> schema = client.tables[tk].schema #multi_mod(client).insert(multi, id <> ":#{tk}", fn ops -> multi_mod(client).insert(multi, id, fn ops -> new_entity = resolve_placeholder_ids(entity, ops) schema.sorcery_insert(struct(schema), new_entity) end) end) end defp build_multi_updates(multi, src, client) do Enum.reduce(src.changes_db, multi, fn {tk, table}, multi -> Enum.reduce(table, multi, fn {id_str, _}, multi when is_binary(id_str) -> multi {id, entity}, multi -> # Every id here should be an integer schema = client.tables[tk].schema multi_mod(client).update(multi, "#{tk}:#{id}", fn prev_ops -> original_entity = get_original_entity(client.db, tk, id, schema) new_entity = resolve_placeholder_ids(entity, prev_ops) cs = schema.sorcery_update(original_entity, new_entity) cs end) end) end) end defp get_original_entity(db, tk, id, schema) do table = Map.get(db, tk, %{}) entity = Map.get(table, id, %{}) defaults = struct(schema) Map.merge(defaults, entity) end defp build_multi_deletes(multi, src, client) do Enum.reduce(src.deletes, multi, fn {tk, id}, multi -> schema = client.tables[tk].schema cs = struct(schema, %{id: id}) multi_mod(client).delete(multi, "#{tk}:#{id}", cs) end) end defp resolve_placeholder_ids(entity, ops) do Enum.reduce(entity, entity, fn {k, v}, acc -> case v do "$sorcery:" <> _ -> new_v = Map.get(ops, v).id Map.put(acc, k, new_v) _ -> acc end end) end # Returns an %Ecto.Multi{} struct defp multi_mod(%{ecto: ecto}) do Module.concat([ecto, "Multi"]) end end