Odd error with String.trim and Ecto update_change

I can’t figure out why I get an error when attempting to trim an empty string input ("") but don’t get an error if the string only has spaces (" ").

The name param is trimmed when the name has a space character:

iex(9)> Ecto.Changeset.cast({%{name: "Bob"}, %{name: :string}}, %{name: " "}, [:name]) |> Ecto.Changeset.update_change(:name, &String.trim/1)
#Ecto.Changeset<
  action: nil,
  changes: %{name: ""},
  errors: [],
  data: %{name: "Bob"},
  valid?: true
>

When the name param is an empty (but not nil) string it crashes. :confused:

iex(10)> Ecto.Changeset.cast({%{name: "Bob"}, %{name: :string}}, %{name: ""}, [:name]) |> Ecto.Changeset.update_change(:name, &String.trim/1) 
** (FunctionClauseError) no function clause matching in String.Break.trim_leading/1    
    
    The following arguments were given to String.Break.trim_leading/1:
    
        # 1
        nil
    
    Attempted function clauses (showing 1 out of 1):
    
        def trim_leading(string) when is_binary(string)
    
    (elixir) lib/elixir/unicode/properties.ex:288: String.Break.trim_leading/1
    (elixir) lib/string.ex:1108: String.trim/1
    (ecto) lib/ecto/changeset.ex:1078: Ecto.Changeset.update_change/3
iex(10)>

Am I missing something?

Elixir 1.9.1 (compiled with Erlang/OTP 22)
Ecto 3.1.7

When you run this you get:

#Ecto.Changeset<
  action: nil,
  changes: %{name: nil},
  errors: [],
  data: %{name: "Bob"},
  valid?: true
>

Then with Ecto.Changeset.update_change(:name, &String.trim/1) you’re calling String.trim(nil) which blows up.

What is happening here is that "" by default is treated by Ecto as an “empty_value” and it converted into nil. If you want "" to be a valid value you can either add it as the default or specify empty_values to not include it which would look like:

Ecto.Changeset.cast({%{name: "Bob"}, %{name: :string}}, %{name: ""}, [:name], empty_values: [])
|> Ecto.Changeset.update_change(:name, &String.trim/1)

#Ecto.Changeset<
  action: nil,
  changes: %{name: ""},
  errors: [],
  data: %{name: "Bob"},
  valid?: true
>

Similar discussion: Casting empty strings is broken · Issue #1684 · elixir-ecto/ecto · GitHub

5 Likes