defmodule MishkaInstaller.Database.Helper do @moduledoc """ This module provides some functions as utility tools to work with a database and other things. """ import Ecto.Changeset require Logger @doc """ If you need to convert database errors into a list, this function can be helpful. One of its uses can be correcting returned errors from the database into a list and converting it into JSON. ## Examples ```elixir MishkaInstaller.Database.Helper.translate_errors(changeset) ``` """ @spec translate_errors(Ecto.Changeset.t()) :: %{optional(atom) => [binary | map]} def translate_errors(changeset) do Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} -> Enum.reduce(opts, msg, fn {key, value}, acc -> String.replace(acc, "%{#{key}}", to_string(value)) end) end) end @doc """ Converting string map to atom map. ## Examples ```elixir MishkaInstaller.Database.Helper.convert_string_map_to_atom_map(%{"name" => "Mishka"}) ``` """ @spec convert_string_map_to_atom_map(map) :: map def convert_string_map_to_atom_map(map) do map |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end) end @doc """ UUID validation for ecto schema. ## Examples ```elixir MishkaInstaller.Database.Helper.validate_binary_id(12) # OR MishkaInstaller.Database.Helper.validate_binary_id("8c512ac2-e002-4589-a93f-b479e46c249d") ``` """ @spec validate_binary_id(Ecto.Changeset.t(), atom, any) :: Ecto.Changeset.t() def validate_binary_id(changeset, field, options \\ []) do validate_change(changeset, field, fn _, uuid -> case uuid(uuid) do {:ok, :uuid, _record_id} -> [] {:error, :uuid} -> [ {field, options[:message] || Gettext.dgettext( MishkaInstaller.gettext(), "mishka_installer", "ID should be as a UUID type." )} ] end end) end @doc """ UUID validation. ## Examples ```elixir MishkaInstaller.Database.Helper.uuid(12) # OR MishkaInstaller.Database.Helper.uuid("8c512ac2-e002-4589-a93f-b479e46c249d") ``` """ @spec uuid(any) :: {:error, :uuid} | {:ok, :uuid, Ecto.UUID.t()} def uuid(id) do case Ecto.UUID.cast(id) do {:ok, record_id} -> {:ok, :uuid, record_id} _ -> {:error, :uuid} end end @doc """ Helper function to keep PID alive for testing Genserver and database. ### Reference - https://elixirforum.com/t/how-to-send-sandbox-allow-for-each-dynamic-supervisor-testing/46422/4 ## Examples ```elixir MishkaInstaller.Database.Helper.allow_if_sandbox(pid) ``` """ def allow_if_sandbox(parent_pid, orphan_msg \\ :stop) do if sandbox_pool?() do monitor_parent(parent_pid, orphan_msg) # this addresses #1 Ecto.Adapters.SQL.Sandbox.allow(MishkaInstaller.repo(), parent_pid, self()) end end defp sandbox_pool?() do MishkaInstaller.repo() == Ecto.Integration.TestRepo end defp monitor_parent(parent_pid, orphan_msg) do # this is part of addressing #2 Process.monitor(parent_pid) if Process.alive?(parent_pid) do :ok else Logger.error("#{inspect(parent_pid)} down when booting #{inspect(self())}") # this addresses #3 # the "throw" will work like an early "return"; see the GenServer docs throw(orphan_msg) end end @doc false def get_parent_pid(state) when is_nil(state.parent_pid), do: :ok def get_parent_pid(state) do if Mix.env() == :test do allow_if_sandbox(state.parent_pid) else :ok end end end