Repository: elixir-ecto/ecto
Pull request #4528 (2024-10-18 17:26:12)

Pass allow_stale opt to assocs
When implementing the soft delete detailed [here](https://dashbit.co/blog/soft-deletes-with-ecto) I ran into errors when soft deleting assocs, it seems we just need to pass the allow_stale option on to the assocs to allow this

tomconroy: Done! I also updated the language slightly and removed the "Only applies to updates." note on insert_or_update as it will now affect inserts with associations. I think this change doesn't affect `delete`, I'm not sure though.

Patch:
diff --git a/lib/ecto/repo.ex b/lib/ecto/repo.ex
index 5214d599..1f0c8da5 100644
--- a/lib/ecto/repo.ex
+++ b/lib/ecto/repo.ex
@@ -1660,9 +1660,9 @@ defmodule Ecto.Repo do
     * `:stale_error_message` - The message to add to the configured
       `:stale_error_field` when stale errors happen, defaults to "is stale".
 
-    * `:allow_stale` - Doesn't error if insert is stale. Defaults to `false`.
+    * `:allow_stale` - Doesn't error when structs are stale. Defaults to `false`.
       This may happen if there are rules or triggers in the database that
-      rejects the insert operation.
+      rejects the insert operation. This option cascades to associations.
 
   See the ["Shared options"](#module-shared-options) section at the module
   documentation for more options.
@@ -1863,7 +1863,7 @@ defmodule Ecto.Repo do
     * `:allow_stale` - Doesn't error if update is stale. Defaults to `false`.
       This may happen if the struct has been deleted from the database before
       the update or if there is a rule or a trigger on the database that rejects
-      the update operation.
+      the update operation. This option cascades to associations.
 
   See the ["Shared options"](#module-shared-options) section at the module
   documentation for more options.
@@ -1909,8 +1909,8 @@ defmodule Ecto.Repo do
     * `:stale_error_message` - The message to add to the configured
       `:stale_error_field` when stale errors happen, defaults to "is stale".
       Only applies to updates.
-    * `:allow_stale` - Doesn't error if delete is stale. Defaults to `false`.
-      Only applies to updates.
+    * `:allow_stale` - Doesn't error when structs are stale. Defaults to `false`.
+      This option cascades to associations.
 
   See the ["Shared options"](#module-shared-options) section at the module
   documentation for more options.
diff --git a/lib/ecto/repo/schema.ex b/lib/ecto/repo/schema.ex
index aedc99cf..6fc92116 100644
--- a/lib/ecto/repo/schema.ex
+++ b/lib/ecto/repo/schema.ex
@@ -977,7 +977,7 @@ defmodule Ecto.Repo.Schema do
   defp assoc_opts([], _opts), do: []
 
   defp assoc_opts(_assocs, opts) do
-    Keyword.take(opts, [:timeout, :log, :telemetry_event, :prefix])
+    Keyword.take(opts, [:timeout, :log, :telemetry_event, :prefix, :allow_stale])
   end
 
   defp process_parents(changeset, user_changeset, assocs, reset_assocs, adapter, opts) do


Test patch:
diff --git a/test/ecto/repo_test.exs b/test/ecto/repo_test.exs
index bb6f0d61..82f5bd99 100644
--- a/test/ecto/repo_test.exs
+++ b/test/ecto/repo_test.exs
@@ -49,7 +49,8 @@ defmodule Ecto.RepoTest do
 
     schema "my_schema_child" do
       field :a, :string
-      belongs_to :my_schema, MySchemaNoPK, references: :n, foreign_key: :n
+      belongs_to :my_schema, MySchema
+      belongs_to :my_schema_no_pk, MySchemaNoPK, references: :n, foreign_key: :n
     end
 
     def changeset(struct, params) do
@@ -1130,6 +1131,23 @@ defmodule Ecto.RepoTest do
       assert {:ok, _} = TestRepo.delete(stale, allow_stale: true)
     end
 
+    test "insert, update allows stale children with :allow_stale option" do
+      child_schema =
+        %MySchemaChild{a: "one"}
+
+      stale =
+        put_in(child_schema.__meta__.context, {:error, :stale})
+        |> Ecto.Changeset.change()
+
+      changeset =
+        %MySchema{id: 1}
+        |> Ecto.Changeset.change()
+        |> Ecto.Changeset.put_assoc(:children, [stale])
+
+      assert {:ok, _} = TestRepo.insert(changeset, allow_stale: true)
+      assert {:ok, _} = TestRepo.update(changeset, allow_stale: true)
+    end
+
     test "insert and delete sets schema prefix with struct" do
       valid = %MySchema{id: 1}