cnck1387

cnck1387

Why does Repo.get and Repo.get_by return the resource or nil instead of an ok or error tuple?

Hi,

Repo.insert, Repo.update and Repo.delete all return {:ok, resource} or {:error, changeset}.

But Repo.get and Repo.get_by return resource or nil.

I also noticed a decent amount of custom code (such as the Programming Phoenix Rumbl example app) prefers to use the tuple style, such as the RumblWeb.Auth.login_by_email_and_pass function returning {:ok, conn} or {:error, _reason, conn}.

That made me very curious. Why doesn’t Repo.get return {:ok, resource} or {:error, :resource_not_found}?

If it’s an API oversight and too late to change, what do you think about adding this custom function to your lib/myapp/repo.ex and always using this instead of get_by?

  def fetch_by(queryable, clauses, opts \\ []) do
    case get_by(queryable, clauses, opts) do
      nil ->
        {:error, :resource_not_found}

      resource ->
        {:ok, resource}
    end
  end

The fetch function (not included here) would do the same thing, except wrap get.

Edit: For clarity, it would be useful to have this because when pattern matching on else when using with it’s a lot more obvious to see what went wrong when you match on something like {:error, :resource_not_found} vs matching on nil.

Most Liked

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Yup. I add this to all my apps repo modules usually:

  def fetch(query) do
    case all(query) do
      [] -> {:error, query}
      [obj] -> {:ok, obj}
      _ -> raise "Expected one or no items, got many items #{inspect(query)}"
    end
  end

Super handy when you use with as well.

13
Post #4
benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Ah well I actually have three functions:

  def fetch_by(query, args) do
    query
    |> where(^args)
    |> fetch
  end

  def fetch(query, id) do
    query
    |> where(id: ^id)
    |> fetch
  end

  def fetch(query) do
    case all(query) do
      [] -> {:error, query}
      [obj] -> {:ok, obj}
      _ -> raise "Expected one or no items, got many items #{inspect(query)}"
    end
  end
benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Yeah there’s a reason my fetch uses all internally because you can also run into issues if your query selects just one column which can be nil.

value = User |> select([u], u.banned_at) |> Repo.get(id)

If value is nil, was the user never banned? or do they not exist? The fetch function I wrote will return {:ok, nil} for a user that exists but the column is nil, and {:error, query} if they don’t exist at all.

I proposed it to the ecto team a while ago, you can find the discussion here: https://github.com/elixir-ecto/ecto/issues/1225

michalmuskala

michalmuskala

Erlang has it’s fair share of weird APIs, I wouldn’t glorify it to the extent you’re doing. Also - in all core elixir cases where there’s a nil default value, you have APIs that return the ok/error tuples as well. I’m not sure why your mind is so boggled.

A selection of the “overlapping types” erlang APIs that one has to deal with fairly regularly:

  • :ets.first/1 with the return of value | :"$end_of_table";
  • :erlang.get/1 with the return type of value | :undefined;
  • :erlang.get_cookie/1 with atom() | nocookie; and probably others

At the same time, there’s a fair share of “split brain” situations with similar APIs returning slightly different things:

  • gb_trees module that uses {value, X} | none,
  • dict and maps that uses {ok, X} | error,
  • lists that use {value, Tuple} | false
  • queue with {value, X} | empty

I actually found very few APIs using undefined.

I’d take the uniform “weird Ruby’ism” of the Elixir APIs with get :: value | nil, fetch :: {:ok, value} | :error, and fetch! :: value | no_return() any time of the day.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

The whole point of one! and get! is to allow the user to opt in to the idea that not having some particular record is a show stopping problem.

While it’s true (and many people have noted this) that returning 0 results isn’t an error in an SQL sense, I don’t see any issue with Ecto providing functions that let you, the programmer, assert that such a response is an error. The whole point of having functions beyond all is to let the program author provide some additional semantics on top of results that are returned.

Where Next?

Popular in Questions Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New

Other popular topics Top

albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

We're in Beta

About us Mission Statement