12
votes

In a Phoenix / Elixir app in this little sanitation function I go 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?

1

1 Answers

25
votes

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)