What is this about?

There are few functions that I end up adding to every single Elixir project I work on. They are not included in Ecto by default but I find them very useful.

fetch/2 and fetch_by/3 are similar to get/3 and get_by/3 but they return {:ok, record} or {:error, :not_found} which makes pattern matching easier.

count/2 is just a shortcut for aggregate/3 (I can never remember the syntax for aggregate/3).

transact/1 is a wrapper around transaction/3 that allows you to use a with statement to perform multiple operations with less code than when using Ecto.Multi. This article by Tom Konidas explains the idea in more detail.

How to?

See the code below:

defmodule MyApp.Repo do
  use Ecto.Repo,
    otp_app: :myapp,
    adapter: Ecto.Adapters.Postgres

  @spec fetch(Ecto.Queryable.t()) :: {:ok, Ecto.Schema.t()} | {:error, :not_found}
  def fetch(query), do: fetch(query, [])

  @spec fetch(Ecto.Queryable.t(), keyword() | any()) ::
          {:ok, Ecto.Schema.t()} | {:error, :not_found}
  def fetch(query, opts) when is_keyword(opts) do
    case one(query, opts) do
      nil -> {:error, :not_found}
      rec -> {:ok, rec}
    end
  end

  def fetch(query, id), do: fetch(query, id, [])

  @spec fetch(Ecto.Queryable.t(), any(), keyword()) ::
          {:ok, Ecto.Schema.t()} | {:error, :not_found}
  def fetch(queryable, id, opts \\ []) do
    case get(queryable, id, opts) do
      nil -> {:error, :not_found}
      record -> {:ok, record}
    end
  end

  @spec fetch_by(Ecto.Queryable.t(), keyword() | map(), keyword()) ::
          {:ok, Ecto.Schema.t()} | {:error, :not_found}
  def fetch_by(queryable, clauses, opts \\ []) do
    case get_by(queryable, clauses, opts) do
      nil -> {:error, :not_found}
      record -> {:ok, record}
    end
  end

  @spec count(Ecto.Queryable.t(), atom) :: term | nil
  def count(queryable, key \\ :id), do: aggregate(queryable, :count, key)

  @spec trasnsact((() -> any()), keyword()) :: {:ok, any()} | {:error, any()}
  def transact(fun, opts \\ []) do
    transaction(
      fn ->
        case fun.() do
          {:ok, value} -> value
          :ok -> :transaction_commited
          {:error, reason} -> rollback(reason)
          :error -> rollback(:transaction_rollback_error)
        end
      end,
      opts
    )
  end
end

References