TIL: Ecto reading after writes

Today I learned that by default, Ecto doesn’t read data back from the database, after writing new or updated data.

The scenario: A trigger in the database, that calculates the new value of a field before insert or update. The person who presented this problem was quite convinced that the trigger wasn’t running in their tests, based on code like the following:

defmodule Folder do
  use Ecto.Schema

  schema "folders" do
    field :name
    field :path # Autocalculated via a PostgreSQL trigger.
  end
end
test "folder paths are autogenerated" do
  folder = Repo.insert %Folder{name: "test"}
  assert folder.id != nil # The folder was inserted successfully
  assert folder.path != nil # But this line was failing.
end

Because they could see paths being generated for folders in their development database, they assumed it was a problem with their test environment setup.

However, Ecto assumes that actual stored data will be unchanged from the data you try to store. For the most part this is a valid assumption, so to optimize data being transmitted back and forth it just returns the data that you passed in, with updated data for any autogenerated fields such as id (usually).

But you can tell Ecto that data for a given field should be read back after any insert/update, with the appropriately-named read_after_writes option for a field. In this case, it would look like so:

defmodule Folder do
  use Ecto.Schema

  schema "folders" do
    field :name
    field :path, read_after_writes: true
  end
end

Caveat: This only works in PostgreSQL, because it will append RETURNING <field_names> to the generated SQL query, and update the data with the newly-returned values.

But in this case, it was exactly what the user wanted and the test now passed successfully. And I learned something!