diff --git a/priv/test_repo/migrations/20260610180000_add_notification_recipients.exs b/priv/test_repo/migrations/20260610180000_add_notification_recipients.exs new file mode 100644 index 00000000..13e0f86b --- /dev/null +++ b/priv/test_repo/migrations/20260610180000_add_notification_recipients.exs @@ -0,0 +1,75 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.TestRepo.Migrations.AddNotificationRecipients do + use Ecto.Migration + + def up do + create table(:notification_recipients, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:order, :bigint) + + add( + :post_id, + references(:posts, + column: :id, + name: "notification_recipients_post_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + + add( + :comment_id, + references(:comments, + column: :id, + name: "notification_recipients_comment_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + + add( + :user_id, + references(:users, + column: :id, + name: "notification_recipients_user_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + + add( + :staff_group_id, + references(:staff_group, + column: :id, + name: "notification_recipients_staff_group_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + + create( + unique_index(:notification_recipients, ["post_id", "user_id"], + name: "notification_recipients_post_user_index", + where: "(post_id IS NOT NULL) AND (user_id IS NOT NULL)" + ) + ) + end + + def down do + drop_if_exists( + unique_index(:notification_recipients, ["post_id", "user_id"], + name: "notification_recipients_post_user_index" + ) + ) + + drop(constraint(:notification_recipients, "notification_recipients_post_id_fkey")) + drop(constraint(:notification_recipients, "notification_recipients_comment_id_fkey")) + drop(constraint(:notification_recipients, "notification_recipients_user_id_fkey")) + drop(constraint(:notification_recipients, "notification_recipients_staff_group_id_fkey")) + drop(table(:notification_recipients)) + end +end diff --git a/test/load_test.exs b/test/load_test.exs index 596769a8..a9a63194 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -8,6 +8,7 @@ defmodule AshPostgres.Test.LoadTest do alias AshPostgres.Test.{ Author, Comment, + NotificationRecipient, Post, PostFollower, Record, @@ -234,6 +235,40 @@ defmodule AshPostgres.Test.LoadTest do assert length(post.first_3_followers) == 2 end + test "many_to_many can sort on a polymorphic partial identity join relationship" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.create!() + + followers = + for i <- 0..2 do + User + |> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true}) + |> Ash.create!() + end + + followers + |> Enum.zip([2, 0, 1]) + |> Enum.each(fn {follower, order} -> + NotificationRecipient + |> Ash.Changeset.for_create(:create, %{ + order: order, + post_id: post.id, + user_id: follower.id + }) + |> Ash.create!() + end) + + [post] = + Post + |> Ash.Query.for_read(:read, %{}) + |> Ash.Query.load(:sorted_notification_users) + |> Ash.read!() + + assert Enum.map(post.sorted_notification_users, & &1.name) == ["user1", "user2", "user0"] + end + test "many_to_many loads work when nested" do source_post = Post diff --git a/test/support/domain.ex b/test/support/domain.ex index ab7db992..b8e517fc 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -39,6 +39,7 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Permalink) resource(AshPostgres.Test.Record) resource(AshPostgres.Test.PostFollower) + resource(AshPostgres.Test.NotificationRecipient) resource(AshPostgres.Test.StatefulPostFollower) resource(AshPostgres.Test.PostWithEmptyUpdate) resource(AshPostgres.Test.DbPoint) diff --git a/test/support/resources/notification_recipient.ex b/test/support/resources/notification_recipient.ex new file mode 100644 index 00000000..3d4bae52 --- /dev/null +++ b/test/support/resources/notification_recipient.ex @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.NotificationRecipient do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "notification_recipients" + repo AshPostgres.TestRepo + + identity_wheres_to_sql(post_user: "(post_id IS NOT NULL) AND (user_id IS NOT NULL)") + end + + identities do + identity(:post_user, [:post_id, :user_id], + where: expr(not is_nil(post_id) and not is_nil(user_id)) + ) + end + + actions do + default_accept(:*) + + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + attribute(:order, :integer, public?: true) + end + + relationships do + belongs_to :post, AshPostgres.Test.Post do + public?(true) + allow_nil?(true) + end + + belongs_to :comment, AshPostgres.Test.Comment do + public?(true) + allow_nil?(true) + end + + belongs_to :user, AshPostgres.Test.User do + public?(true) + allow_nil?(true) + end + + belongs_to :staff_group, AshPostgres.Test.StaffGroup do + public?(true) + allow_nil?(true) + end + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 4c628ba4..7520d8b2 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -884,6 +884,11 @@ defmodule AshPostgres.Test.Post do has_many(:post_followers, AshPostgres.Test.PostFollower) + has_many :user_notification_recipients, AshPostgres.Test.NotificationRecipient do + filter(expr(not is_nil(user_id))) + sort(order: :asc) + end + # For testing join_relationship limit inheritance has_many :top_three_post_followers, AshPostgres.Test.PostFollower do sort(order: :asc) @@ -918,6 +923,15 @@ defmodule AshPostgres.Test.Post do sort: [Ash.Sort.expr_sort(parent(post_followers.order), :integer)] ) + many_to_many(:sorted_notification_users, AshPostgres.Test.User, + public?: true, + through: AshPostgres.Test.NotificationRecipient, + join_relationship: :user_notification_recipients, + source_attribute_on_join_resource: :post_id, + destination_attribute_on_join_resource: :user_id, + sort: [Ash.Sort.expr_sort(parent(user_notification_recipients.order), :integer)] + ) + many_to_many :tags, AshPostgres.Test.Tag do public?(true) through(AshPostgres.Test.PostTag)