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
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.
benwilson512
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
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
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/1with the return ofvalue | :"$end_of_table"; -
:erlang.get/1with the return type ofvalue | :undefined; -
:erlang.get_cookie/1withatom() | 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_treesmodule that uses{value, X} | none, -
dictandmapsthat uses{ok, X} | error, -
liststhat use{value, Tuple} | false -
queuewith{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
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.







