iex(2)> TomKonidas.get_article("repo-fetch")

Repo.fetch/3

Every project I find myself writing the same bit of code. I love to use case and with statements for conditional control flow across my code base and much prefer to use ok/error tuples over pattern matching on nil.

I find it weird that Ecto does not have this included in their API already, however luckily for us, it is easy to key in and extend the Repo module.

What I add to every Ecto project

We can use what Ecto gives us out of the box and just extend our Repo module with these two little wrapper functions.

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

  @spec fetch(Ecto.Queryable.t(), binary(), 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
end

Now calling Repo.fetch/3 feels like a native Ecto implementation and we do not ever need to worry about custom logic as we are just re-using what is already there and passing on all the arguments.