Ecto schema default value is not included in changeset changes
Asked Answered
M

1

5

Consider the following schema:

defmodule EctoBug.Post do
  use Ecto.Schema
  import Ecto.Changeset

  schema "posts" do
    field :title, :string, default: "test"

    timestamps()
  end

  def changeset(post, attrs) do
    post
    |> cast(attrs, [:title])
    |> validate_required([:title])
  end
end

If I do

changeset = EctoBug.Post.changeset(%EctoBug.Post{}, %{title: "test"})

title field is not present in changes:

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

I couldn't find anything on this behavior.

Is it a bug?

Metzgar answered 13/10, 2018 at 13:3 Comment(1)
This is a desired behaviour. Default values are being set inside the database, which is way more performant, than passing them within create command.Frowsty
M
6

Ecto's changeset structure holds changes applied to a schema/struct. The reason the default doesn't appear as a change is because when you do %EctoBug.Post{}, it's populated with the default values you set on the schema. Then when you cast the params, since the original and the cast values for the field are the same, it's not really a change and isn't marked as such.

If you do

changeset = EctoBug.Post.changeset(%EctoBug.Post{}, %{title: "test"})
Ecto.Changeset.get_field(changeset, :title)

You should see that title is set. Although Ecto.Changeset.get_change(changeset, :title) will give you nil since it hasn't been changed from the original struct that was given initially.

If you provide a different value than the default for the field title you should see that it will be tracked as a change.

Because it works this way, now if you had a record that you were trying to update instead of creating, if you cast against that record some params that were actually the same as it already had, ecto can skip trying to update the record, since it could see that nothing had changed. In the case of an insert, even if it has no changes, Ecto will attempt to insert it.

Malca answered 13/10, 2018 at 16:31 Comment(2)
Thank you for your answer. I was doing custom validation like this def validate_this(%Ecto.Changeset{changes: %{field1: field1}, valid?: true}) but with default value being removed it is not possible. So I decided to remove default from a field I was validating.Metzgar
@Rustam, in this case you might look into hexdocs.pm/ecto/Ecto.Changeset.html#fetch_field/2 , as this tries to extract by order, a change, then the field if no change for that value was made and presents it as {:changes, value} or {:data, value} accordingly, so if it gives you back {_, nil} you know that neither the field has a change nor value.Malca

© 2022 - 2024 — McMap. All rights reserved.