lib/mnesiac/store.ex

defmodule Mnesiac.Store do
  @moduledoc """
  This module defines a mnesiac store and contains overridable callbacks.
  """

  @doc """
  This function returns the store's configuration as a keyword list.
  For more information on the options supported here, see mnesia's documentation.
  ## Examples
  ```elixir
  iex> store_options()
  [attributes: [...], index: [:topic_id], disc_copies: [node()]]
  ```
  **Note**: Defining `:record_name` in `store_options()` will set the mnesia table name to the same.
  """
  @callback store_options() :: term

  @doc """
  This function is called by mnesiac either when it has no existing data to use or copy and will initialise a table
  ## Default Implementation
  ```elixir
  def init_store do
    :mnesia.create_table(Keyword.get(store_options(), :record_name, __MODULE__), store_options())
  end
  ```
  """
  @callback init_store() :: term

  @doc """
  This function is called by mnesiac when it joins a mnesia cluster and data for this store is found on the remote node in the cluster that is being connected to.
  ## Default Implementation
  ```elixir
  def copy_store do
    for type <- [:ram_copies, :disc_copies, :disc_only_copies] do
      value = Keyword.get(store_options(), type, [])

      if Enum.member?(value, node()) do
        :mnesia.add_table_copy(Keyword.get(store_options(), :record_name, __MODULE__), node(), type)
      end
    end
  end
  ```
  """
  @callback copy_store() :: term

  @doc ~S"""
  This function is called by mnesiac when it has detected data for a table on both the local node and the remote node of the cluster it is connecting to.
  ## Default Implementation
  ```elixir
  def resolve_conflict(cluster_node) do
    table_name = Keyword.get(store_options(), :record_name, __MODULE__)
    Logger.info(fn -> "[mnesiac:#{node()}] #{inspect(table_name)}: data found on both sides, copy aborted." end)

    :ok
  end
  ```
  **Note**: The default implementation for this function is to do nothing.
  """
  @callback resolve_conflict(node()) :: term

  @optional_callbacks copy_store: 0, init_store: 0, resolve_conflict: 1

  defmacro __using__(_) do
    quote do
      require Logger
      @behaviour Mnesiac.Store

      def init_store do
        :mnesia.create_table(Keyword.get(store_options(), :record_name, __MODULE__), store_options())
      end

      def copy_store do
        for type <- [:ram_copies, :disc_copies, :disc_only_copies] do
          value = Keyword.get(store_options(), type, [])

          if Enum.member?(value, node()) do
            :mnesia.add_table_copy(Keyword.get(store_options(), :record_name, __MODULE__), node(), type)
          end
        end
      end

      def resolve_conflict(cluster_node) do
        table_name = Keyword.get(store_options(), :record_name, __MODULE__)
        _ = Logger.info(fn -> "[mnesiac:#{node()}] #{inspect(table_name)}: data found on both sides, copy aborted." end)

        :ok
      end

      defoverridable Mnesiac.Store
    end
  end
end