Ecto: Ecto.apply_changes not taking account of database default

Created on 25 Apr 2018  路  7Comments  路  Source: elixir-ecto/ecto

Environment

  • Elixir version (elixir -v): 1.6.3
  • Database and version: Postgres 10.3
  • Ecto version: 2.2.9
  • Database adapter and version: 0.13.5
  • Operating system: unix

Current behavior

I have an ecto schema backed by a table that has a non_null column with a default. I want to add a validation that will check that column is in an enum of values:

    changeset
    |> Validations.validate_field_in_enum(:lead_status, ["active", "inactive"])
  def validate_field_in_enum(changeset, field, enum) do
    field =
      changeset
      |> Ecto.Changeset.apply_changes()
      |> Map.get(field)

    if field in enum do
      changeset
    else
      add_error(changeset, field, "Invalid #{field}", validation: field)
    end
  end

The problem is when I call Ecto.Changeset.apply_changes(), the struct returned will not include the default value the DB will eventually give the column I am checking. That means, when the record is being created without that specific field in the changes, I can't validate it. As the field comes back as nil.

Expected behavior

That the struct returned will contain that value.

Most helpful comment

Since the only way to access the database is through Repo, when it's not used we don't touch database (this is one of the basic guarantees of Ecto). This means there isn't even a way to make it work even if we wanted to.

Probably the only way to read the database defaults (if you really need to do this) would be to insert the value in a transaction just to immediately rollback.

All 7 comments

So say a default on a field is something like "someDatabaseFunction()", what would you expect the default to be? Defaults are database-side, not Elixir side, as only the database can truly know. If a database can do the validation of something then it probably should do the validation. :-)

Since the only way to access the database is through Repo, when it's not used we don't touch database (this is one of the basic guarantees of Ecto). This means there isn't even a way to make it work even if we wanted to.

Probably the only way to read the database defaults (if you really need to do this) would be to insert the value in a transaction just to immediately rollback.

Sorry to re-open the issue, but it appears that it doesn't seem to work with Repo.insert either.

if I do something like:

    %MyStruct{}
    |> create_and_validate_changeset(%{foo: "bar"})
    |> MyApp.Repo.insert()

The struct I am returned doesn't reflect the DB - the default boolean field gets set to null in the DB, but not in the struct returned from the Repo.insert.

I can fix this by adding a default to the model, is that what I am meant to do? Or should the insert return what the DB has?

Yes, use :default or read_after_write: true for a given field in your schema definition.

Ahh yes read_after_writes that鈥檚 what i was trying to remember!! Thanks

Why is read_after_writes not the default though?

It increases IO and is not suppprted by all databases.

Jos茅 Valim
www.plataformatec.com.br
Skype: jv.ptec
Founder and Director of R&D

Was this page helpful?
0 / 5 - 0 ratings