Idiomatic approach to run queries sequentially that can return early

I’m working on an Elixir open-source project in which I have an Accounts context with a function find_or_create to find or create an account in the database with the metadata passed by OAuth providers like GitHub.

To find the user I’d need to run a sequence of queries and return early when I find the user. Something along the lines of:

  1. If a user with the same GitHub uid exists, then return the account associated to the user.
  2. Otherwise, if a user exist with the same email as GitHub’s, return the account associated to th euser.
  3. If none of the above is met, then create the user along with its account.

In other programming languages (I’m new to Elixir), I’d write something along the lines of:

if user do
  return user.account
end

But the above is not possible in Elixir so I was wondering what’d be the Elixir way of tackling it. Note that I don’t need to run the second query if the first yields a value.

Thanks beforehand

with can do that.

def upsert_user(…) do
  with nil <- by_uid(…),
       nil <- by_email(…) do
    create(…)
  end
end
3 Likes

there are a few options on how to handle that. in my opinion the best one would be avoiding a single function to find or create a user.

besides the with approach suggested by @LostKobrakai you could have a pipe sequence like

uid
|> maybe_find_uid()
|> maybe_find_email(email)
|> maybe_create(params)

...

defp maybe_find_email(user, _email) when not is_nil(user), do: user
defp maybe_find_email(nil, email) do
...
end

you also could do a reduce_while approach:

[&find_by_uid/1, &find_by_email/1, &create/1]
|> Enum.reduce_while(params, fn func, params -> 
case func.(params) do
  {:ok, user} -> {:halt, user}
  nil -> {:cont, params}
  {:error, changeset} -> {:halt, changeset}
end
end)