lib/storage/adapters/ecto/ecto_adapter.ex

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