Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions lib/ash/changeset/changeset.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5608,6 +5608,12 @@ defmodule Ash.Changeset do
doc:
"Validates that any referenced entities exist *before* the action is being performed, using the provided domain for the read."
],
eager_validate?: [
type: :boolean,
default: false,
doc:
"Validates that any referenced entities exist *before* the action is being performed, using the domain configured on the related resource."
Comment on lines 5609 to +5615
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new eager_validate? option doc doesn’t mention that it will raise when the destination resource has no configured domain (and that eager_validate_with can be used instead / takes precedence). Consider updating the option docs to reflect the actual behavior so callers know when/why it errors.

Suggested change
"Validates that any referenced entities exist *before* the action is being performed, using the provided domain for the read."
],
eager_validate?: [
type: :boolean,
default: false,
doc:
"Validates that any referenced entities exist *before* the action is being performed, using the domain configured on the related resource."
"Validates that any referenced entities exist *before* the action is being performed, using the provided domain for the read. Takes precedence over `eager_validate?`."
],
eager_validate?: [
type: :boolean,
default: false,
doc:
"Validates that any referenced entities exist *before* the action is being performed, using the domain configured on the related resource. If the related resource has no configured domain, this raises unless `eager_validate_with` is provided."

Copilot uses AI. Check for mistakes.
],
on_no_match: [
type: :any,
default: :ignore,
Expand Down Expand Up @@ -6018,6 +6024,22 @@ defmodule Ash.Changeset do
add_error(changeset, error)

relationship ->
{opts, keyword_opts} =
if opts.eager_validate? && !opts.eager_validate_with do
domain = Ash.Resource.Info.domain(relationship.destination)

if !domain do
raise ArgumentError,
"Cannot use `eager_validate?: true` because #{inspect(relationship.destination)} " <>
"does not have a domain configured. Use `eager_validate_with: YourDomain` instead."
end
Comment on lines +6031 to +6035
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new branch raises when eager_validate?: true is used and the destination resource has no configured domain. There are similar tests for identity eager_check domain inference errors; adding a regression test for this raise would help prevent accidental behavior changes and ensure the error message stays stable.

Copilot uses AI. Check for mistakes.

new_opts = %{opts | eager_validate_with: domain}
{new_opts, ManageRelationshipOpts.to_options(new_opts)}
else
{opts, keyword_opts}
end
Comment on lines +6027 to +6041
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keyword_opts is generated before opts.meta is augmented with :inputs_was_list? (5983-5987), but in the eager_validate? branch you regenerate keyword_opts after modifying opts. This makes the stored options inconsistent depending on whether eager_validate? is set, and can affect later error-path behavior that depends on opts[:meta][:inputs_was_list?] (see Ash.Actions.ManagedRelationships.set_error_path/4). Consider moving the to_options/1 call to after the opts.meta update (and avoid conditionally regenerating it here) so all calls get consistent meta in the stored relationship opts.

Copilot uses AI. Check for mistakes.

key =
opts.value_is_key ||
changeset.resource
Expand Down
24 changes: 24 additions & 0 deletions test/changeset/changeset_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,30 @@ defmodule Ash.Test.Changeset.ChangesetTest do
)
end

test "upsert with many_to_many relationships can eager validate with eager_validate?" do
Category
|> Ash.Changeset.for_create(:create, %{name: "foo"})
|> Ash.create!()

assert %{valid?: false, errors: [%Ash.Error.Query.NotFound{}]} =
Post
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:categories, [%{name: "foo"}, %{name: "bar"}],
on_lookup: :relate,
eager_validate?: true,
use_identities: [:unique_name]
)

assert %{valid?: true} =
Post
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:categories, [%{name: "foo"}],
on_lookup: :relate,
eager_validate?: true,
use_identities: [:unique_name]
)
end

test "it creates related entities" do
post1 = %{title: "title"}
post2 = %{title: "title"}
Expand Down
Loading