diff --git a/priv/resource_snapshots/test_repo/comment_likes/20260220104824_dev.json b/priv/resource_snapshots/test_repo/comment_likes/20260220104824_dev.json new file mode 100644 index 00000000..0ae365f9 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comment_likes/20260220104824_dev.json @@ -0,0 +1,82 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "comment_like_author_fkey", + "on_delete": "delete", + "on_update": "update", + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "scale": null, + "size": null, + "source": "author_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "comment_like_comment_fkey", + "on_delete": "delete", + "on_update": "update", + "primary_key?": true, + "schema": "public", + "table": "comments" + }, + "scale": null, + "size": null, + "source": "comment_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "create_table_options": null, + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "84EF2268196E984A63CA6547634850899334D1B15F2AE7B1A9C04A17566ABAB4", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "comment_likes" +} diff --git a/priv/resource_snapshots/test_repo/comment_likes/20260220104824_dev.json.license b/priv/resource_snapshots/test_repo/comment_likes/20260220104824_dev.json.license new file mode 100644 index 00000000..c5bd9989 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comment_likes/20260220104824_dev.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2019 ash_postgres contributors + +SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20260220104823_migrate_resources70_dev.exs b/priv/test_repo/migrations/20260220104823_migrate_resources70_dev.exs new file mode 100644 index 00000000..afd8328b --- /dev/null +++ b/priv/test_repo/migrations/20260220104823_migrate_resources70_dev.exs @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.TestRepo.Migrations.MigrateResources70 do + @moduledoc """ + Updates resources based on their most recent snapshots. + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + create table(:comment_likes, primary_key: false) do + add( + :author_id, + references(:authors, + column: :id, + name: "comment_like_author_fkey", + type: :uuid, + prefix: "public", + on_delete: :delete_all, + on_update: :update_all + ), + primary_key: true, + null: false + ) + + add( + :comment_id, + references(:comments, + column: :id, + name: "comment_like_comment_fkey", + type: :uuid, + prefix: "public", + on_delete: :delete_all, + on_update: :update_all + ), + primary_key: true, + null: false + ) + end + end + + def down do + drop(constraint(:comment_likes, "comment_like_author_fkey")) + + drop(constraint(:comment_likes, "comment_like_comment_fkey")) + + drop(table(:comment_likes)) + end +end diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index a0b85b53..0ff6d111 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -5,7 +5,7 @@ defmodule AshSql.AggregateTest do use AshPostgres.RepoCase, async: false import ExUnit.CaptureIO - alias AshPostgres.Test.{Author, Chat, Comment, Organization, Post, Rating, User} + alias AshPostgres.Test.{Author, Chat, Comment, CommentLike, Organization, Post, Rating, User} require Ash.Query require Ash.Sort @@ -469,6 +469,59 @@ defmodule AshSql.AggregateTest do |> Ash.Query.load(:count_of_comments_called_match) |> Ash.read_one!() end + + test "organization like aggregates count likes and dedupe unique likers" do + org = + Organization + |> Ash.Changeset.for_create(:create, %{name: "The Org"}) + |> Ash.create!() + + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) + |> Ash.create!() + + comment_1 = + Comment + |> Ash.Changeset.for_create(:create, %{title: "comment 1"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + comment_2 = + Comment + |> Ash.Changeset.for_create(:create, %{title: "comment 2"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + author_1 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "A", last_name: "One"}) + |> Ash.create!() + + author_2 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "B", last_name: "Two"}) + |> Ash.create!() + + [{comment_1, author_1}, {comment_2, author_1}, {comment_1, author_2}] + |> Enum.each(fn {comment, author} -> + CommentLike + |> Ash.Changeset.for_create(:create, %{}) + |> Ash.Changeset.manage_relationship(:comment, comment, type: :append_and_remove) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Ash.create!() + end) + + loaded_org = + Organization + |> Ash.Query.filter(id == ^org.id) + |> Ash.Query.load([:org_likes, :unique_org_likers]) + |> Ash.read_one!() + + assert loaded_org.org_likes == 3 + assert loaded_org.unique_org_likers == 2 + end end describe "exists" do diff --git a/test/support/domain.ex b/test/support/domain.ex index 4a4ea01b..b732d9f5 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -15,6 +15,7 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Comedian) resource(AshPostgres.Test.Comment) + resource(AshPostgres.Test.CommentLike) resource(AshPostgres.Test.CommentLink) resource(AshPostgres.Test.IntegerPost) resource(AshPostgres.Test.Rating) diff --git a/test/support/resources/comment.ex b/test/support/resources/comment.ex index 9c243c2b..d764fd71 100644 --- a/test/support/resources/comment.ex +++ b/test/support/resources/comment.ex @@ -100,6 +100,10 @@ defmodule AshPostgres.Test.Comment do public?(true) end + has_many(:better_likes, AshPostgres.Test.CommentLike) do + public?(true) + end + many_to_many(:linked_comments, AshPostgres.Test.Comment) do public?(true) through(AshPostgres.Test.CommentLink) diff --git a/test/support/resources/comment_like.ex b/test/support/resources/comment_like.ex new file mode 100644 index 00000000..d006bd31 --- /dev/null +++ b/test/support/resources/comment_like.ex @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.CommentLike do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer, + authorizers: [ + Ash.Policy.Authorizer + ] + + policies do + bypass action_type(:read) do + # Check that the comment is in the same org (via post) as actor + authorize_if(relates_to_actor_via([:post, :organization, :users])) + end + end + + postgres do + table "comment_likes" + repo(AshPostgres.TestRepo) + + references do + reference(:author, + on_delete: :delete, + on_update: :update, + name: "comment_like_author_fkey" + ) + + reference(:comment, + on_delete: :delete, + on_update: :update, + name: "comment_like_comment_fkey" + ) + end + end + + actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) + end + + relationships do + belongs_to(:author, AshPostgres.Test.Author) do + allow_nil?(false) + public?(true) + primary_key?(true) + end + + belongs_to(:comment, AshPostgres.Test.Comment) do + allow_nil?(false) + public?(true) + primary_key?(true) + end + end +end diff --git a/test/support/resources/organization.ex b/test/support/resources/organization.ex index 2919b176..48ebfcb0 100644 --- a/test/support/resources/organization.ex +++ b/test/support/resources/organization.ex @@ -34,6 +34,14 @@ defmodule AshPostgres.Test.Organization do count :no_cast_open_posts_count, :posts do filter(expr(status_enum_no_cast != :closed)) end + + count :org_likes, [:posts, :comments, :better_likes] do + uniq?(true) + end + + count :unique_org_likers, [:posts, :comments, :better_likes, :author] do + uniq?(true) + end end actions do