From 57294266998a014cc7bc2c7dd3e85d0c40ec15eb Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 24 Mar 2026 13:36:31 +0100 Subject: [PATCH] fix: recompute changed? after setup_managed_belongs_to_relationships When manage_relationship is called from before_transaction or before_action hooks on update actions, the FK attribute gets set via force_change_attribute inside setup_managed_belongs_to_relationships. However, the changed? flag was computed before this point, so it remained false, causing the DB update to be skipped entirely. The related record was created but the source record's FK was never persisted. Recompute changed? after setup_managed_belongs_to_relationships returns so that FK changes from managed relationships are detected. --- lib/ash/actions/update/update.ex | 14 ++++++++++++ test/manage_relationship_test.exs | 37 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/lib/ash/actions/update/update.ex b/lib/ash/actions/update/update.ex index f5c6b9d16..98c5b9e29 100644 --- a/lib/ash/actions/update/update.ex +++ b/lib/ash/actions/update/update.ex @@ -471,8 +471,22 @@ defmodule Ash.Actions.Update do {:error, error} {changeset, manage_instructions} -> + # Recompute changed? after setup_managed_belongs_to_relationships + # may have set FK attributes via force_change_attribute (e.g. when + # manage_relationship is called from before_transaction hooks on + # update actions). Without this, the changed? flag computed before + # the func callback would be false, causing the DB update to be + # skipped entirely. + changed? = + Ash.Changeset.changing_attributes?(changeset) or + not Enum.empty?(changeset.atomics) + changeset = changeset + |> Ash.Changeset.put_context( + :changed?, + changeset.context[:changed?] || changed? + ) |> Ash.Changeset.require_values( :update, true diff --git a/test/manage_relationship_test.exs b/test/manage_relationship_test.exs index 8d8f26eb9..40c26dce5 100644 --- a/test/manage_relationship_test.exs +++ b/test/manage_relationship_test.exs @@ -252,6 +252,22 @@ defmodule Ash.Test.ManageRelationshipTest do change manage_relationship(:parent_resource, on_match: :update) end + + update :update_create_parent_in_hook do + require_atomic? false + + change fn changeset, _context -> + Ash.Changeset.before_transaction(changeset, fn changeset -> + Ash.Changeset.manage_relationship( + changeset, + :parent_resource, + %{name: "created-in-hook"}, + type: :create, + on_no_match: :create + ) + end) + end + end end attributes do @@ -1289,4 +1305,25 @@ defmodule Ash.Test.ManageRelationshipTest do assert names == ["new1", "existing1", "new2", "existing2"] end end + + describe "manage_relationship called from before_transaction hook on update" do + test "creates related record via belongs_to when manage_relationship is called in before_transaction" do + related = + RelatedResource + |> Ash.Changeset.for_create(:create, %{required_attribute: "test"}) + |> Ash.create!() + + assert related.parent_resource_id == nil + + updated = + related + |> Ash.Changeset.for_update(:update_create_parent_in_hook, %{}) + |> Ash.update!() + + assert updated.parent_resource_id != nil + + {:ok, loaded} = Ash.load(updated, :parent_resource) + assert loaded.parent_resource.name == "created-in-hook" + end + end end