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