3
votes

Is it possible to check if a string parameter being passed in is an integer? For example I need to retrieve a model based either on its id ("12345") or external_id ("eUv9wWzZ48bMZsuII6ivCle2NHgIEPoMLWC9ioDV"). Is this possible to achieve? I tried is_integer but that returns false as it doesn't try to parse a string.

def call(%{params: %{"id" => id}} = conn, module) when is_atom(module) and is_integer(id) do
    ZB.Repo.get!(module, id)
  |> check(conn)
end
def call(%{params: %{"id" => id}} = conn, module) when is_atom(module) do
  ZB.Repo.get_by!(module, external_id: id)
end
2
You should select @mudasobwa's answer instead of mine; mine is incorrect as he explained. :) - Dogbert
@Dogbert honestly, yours is perfectly correct, answering the question stated :) - Aleksei Matiushkin

2 Answers

5
votes

This is an XY problem and I believe you are trying to solve inexisting issue in an incorrect way :)

According to what I can see from your input, external_id is supposed to be the alphanumeric string of length 40. The keyword here is alphanumeric. That said, 40 times "1" might be an extenral_id. And you know what? It is perfectly parsed by Integer.parse/2:

iex|1 ▶ ["1"] |> List.duplicate(40) |> Enum.join |> Integer.parse
#⇒ {1111111111111111111111111111111111111111, ""}

That said, checking for integer is invalid in this context, because it might result in false positive.

What you do actually need to do, is to try to get the record by external_id, possibly based on it’s length or something, and fallback to id if not succeeded. Also, the length of binary might be indeed checked in guard (implicitly, by introducing an intermediate wrapper binary ⇒ bitstring.)

def call(%{params: %{"id" => id}} = conn, module) when is_atom(module) do
  case ZB.Repo.get_by(module, external_id: id) do
    {:ok, record} -> record
    _ -> ZB.Repo.get!(module, id) |> check(conn)
  end
end
5
votes

No, this can't be done in a guard. The functions you can call in a guard is limited and does not include any function to check whether a string contains only digits.

You can instead use Integer.parse/1 in the body:

def call(%{params: %{"id" => id}} = conn, module) when is_atom(module) do
  case Integer.parse(id) do
    {_, ""} -> 
      ZB.Repo.get!(module, id) |> check(conn)
    
    _ -> 
      ZB.Repo.get_by!(module, external_id: id)
  end
end