Ecto, empty_values, nil and default

Hi there!

I have a CRUD api for model that requires two different behaviours for missing data. For example, when IBAN is missing I should save NULL in the database, but when I have second line of address empty, I should save empty string.

I thought that using defaults could be a suitable solution.

create table(:my_models) do
  add :iban, :string, null: true
  add :address2, :string, null: false

schema "my_models" do
  field :iban, :string
  field :address2, :string, default: ""

The strange behavior is that when I try to use my changeset with params like this:

%{"iban" => "", "address2" => ""}

I get changeset as expected with %{iban: nil, address2: ""} Default empty_values have empty string in them, so both iban and address2 are correctly changed to their default values. However for this:

%{"iban" => "", "address2" => nil} #edited

I would expect the same, but I get %{iban: nil, address2: nil}. I checked Ecto.Changeset code and default values are used only when field is in a list of empty values, but not when it is nil. What is the reasoning behind it? Or is it just an oversight and nil should be in a default list of empty values?

If I understand this correctly: address2 is not sent and we are still making it nil based on the default value in the struct? If so, I would say that’s a bug, we should only apply the empty_values to parameters fields.

I am sorry, Looks like I was passing the nil in explicit way. (Edited original question) and I didn’t make clear that I am trying to insert it to the database.

To reiterate I have a field address2 with default value "" and there are couple of cases:

  • I have some data in parameters params = %{"address2" => "some address line"}
  • I create changeset and it sets the address field correctly %{changes: %{address2: "some address line"}}
  • I insert it to the database and it works as expected
  • I am passing there empty string in parameters params = %{"address2" => ""}
  • Changeset checks that empty string is in empty_values, so it is not in changes %{changes: %{}}
  • I insert to the database and address2 has value of empty string, because of default

I don’t pass the parameter at all params = %{}
Works exactly like point 2.

I pass nil as a parameter value: params = %{"address2" => nil}

  • nil is not in empty_values, so it is not removed from changeset, %{changes: {address2: nil}}
  • when I insert to database, address2 is set to NULL.

Because of case 4. I was wondering why nil isn’t one of the empty_values by default. That would make empty string in parameters equivalent to nil in parameters. %{"field" => ""} and %{"field" => nil} would be the same.

While writing this reply I realized that empty values are just for string representations so putting there nil by default wouldn’t make sense.

In summary: there is no bug, everything works, I just go confused :stuck_out_tongue: