Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test values of structs in function guard clauses?

In a Phoenix / Elixir app in this little sanitation function I encountered a problem when the user didn't enter an email.

I am using a struct to deal with the data. And a simple sanitation function that (for now) just strips whitespace and updates the struct (map). So far it worked well, but when the field email is nil, I get an error.

** (FunctionClauseError) no function clause matching in String.Unicode.strip/1

So I introduced a guard clause to check for this case and then only sanitize the username.

defmodule MyApp.User do
  defstruct username: nil, email: nil, password: nil, hashed_password: nil

  # Sanitizing input without guard clause
  def sanitize_user(user) do
    %{user | username: String.strip(user.username), email: String.strip(user.email)}
  end
 
  # Sanitizing input with guard clause
  def sanitize_user(user) when is_nil(user.email) do
    %{user | username: String.strip(user.username)}
  end

  def sanitize_user(user) when is_binary(user.email) do
    %{user | username: String.strip(user.username), email: String.strip(user.email)}
  end
end

Now I'm getting an error at compile time:

** (CompileError) web/models/user.ex:54: cannot invoke remote function Access.get/2 inside guard

I guess it is because the value for this field is not available at compile time. Or something like that.

How to solve this? How to test for values of structs in guard clauses?

like image 962
Ole Spaarmann Avatar asked Nov 27 '15 14:11

Ole Spaarmann


1 Answers

Your assumptions about the values not being available at compile time is correct. You can however pattern match the values in your map and use them in a guard.

You could write your function like:

  def sanitize_user(%MyApp.User{email: nil} = user) do
    %{user | username: String.strip(user.username)}
  end
  def sanitize_user(%MyApp.User{email: email} = user) when is_binary(email) do
    %{user | username: String.strip(user.username), email: String.strip(user.email)}
  end

You can also match on a map without the User struct if you want to allow a other structs (unlikely in this case) to be passed:

  def sanitize_user(%{email: nil} = user)
like image 155
Gazler Avatar answered Sep 20 '22 13:09

Gazler