diff --git a/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json b/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json new file mode 100644 index 00000000..16e8af1a --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json @@ -0,0 +1,124 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "\"active\"", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "state", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": true, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": false, + "strategy": "context" + }, + "name": "non_multitenant_post_multitenant_links_source_id_fkey", + "on_delete": "delete", + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "multitenant_posts" + }, + "scale": null, + "size": null, + "source": "source_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": true, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "non_multitenant_post_multitenant_links_dest_id_fkey", + "on_delete": "delete", + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "scale": null, + "size": null, + "source": "dest_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "F19D22A316D43A64BF2F1E052F545346BD0BCBC660E8EF559D1D4D73D8969A4D", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "non_multitenant_post_multitenant_links_unique_link_index", + "keys": [ + { + "type": "atom", + "value": "source_id" + }, + { + "type": "atom", + "value": "dest_id" + } + ], + "name": "unique_link", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": false, + "strategy": "context" + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "non_multitenant_post_multitenant_links" +} \ No newline at end of file diff --git a/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs b/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs new file mode 100644 index 00000000..42a6f5a3 --- /dev/null +++ b/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs @@ -0,0 +1,78 @@ +defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources7 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(:non_multitenant_post_multitenant_links, primary_key: false, prefix: prefix()) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:state, :text, default: "active") + + add( + :source_id, + references(:multitenant_posts, + column: :id, + name: "non_multitenant_post_multitenant_links_source_id_fkey", + type: :uuid, + prefix: prefix(), + on_delete: :delete_all + ), + null: false + ) + + add( + :dest_id, + references(:posts, + column: :id, + name: "non_multitenant_post_multitenant_links_dest_id_fkey", + type: :uuid, + prefix: "public", + on_delete: :delete_all + ), + null: false + ) + end + + create(index(:non_multitenant_post_multitenant_links, [:source_id])) + + create(index(:non_multitenant_post_multitenant_links, [:dest_id])) + + create( + unique_index(:non_multitenant_post_multitenant_links, [:source_id, :dest_id], + name: "non_multitenant_post_multitenant_links_unique_link_index" + ) + ) + end + + def down do + drop_if_exists( + unique_index(:non_multitenant_post_multitenant_links, [:source_id, :dest_id], + name: "non_multitenant_post_multitenant_links_unique_link_index" + ) + ) + + drop_if_exists(index(:non_multitenant_post_multitenant_links, [:dest_id])) + + drop_if_exists(index(:non_multitenant_post_multitenant_links, [:source_id])) + + drop( + constraint( + :non_multitenant_post_multitenant_links, + "non_multitenant_post_multitenant_links_source_id_fkey" + ) + ) + + drop( + constraint( + :non_multitenant_post_multitenant_links, + "non_multitenant_post_multitenant_links_dest_id_fkey" + ) + ) + + drop(table(:non_multitenant_post_multitenant_links, prefix: prefix())) + end +end diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 0dd99520..89034434 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -2,7 +2,7 @@ defmodule AshPostgres.Test.MultitenancyTest do use AshPostgres.RepoCase, async: false require Ash.Query - alias AshPostgres.MultitenancyTest.{CompositeKeyPost, NamedOrg, Org, Post, User} + alias AshPostgres.MultitenancyTest.{CompositeKeyPost, NamedOrg, Org, Post, User, NonMultitenantPostMultitenantLink} alias AshPostgres.Test.Post, as: GlobalPost setup do @@ -226,6 +226,24 @@ defmodule AshPostgres.Test.MultitenancyTest do ) end + test "loading non multitenant resource across a many_to_many works", %{org1: org1} do + post = Post + |> Ash.Changeset.for_create(:create, %{name: "foo"}) + |> Ash.Changeset.set_tenant(org1) + |> Ash.create!() + + GlobalPost + |> Ash.Changeset.for_create(:create, %{title: "fred"}) + |> Ash.create!() + + NonMultitenantPostMultitenantLink + |> Ash.Changeset.for_create(:create, %{source_id: post.id, dest_id: global_post.id}, tenant: org1) + |> Ash.create!() + + post |> Ash.load!([:linked_non_multitenant_posts_through_multitenant_link], tenant: org1) |> IO.inspect() + end + + test "manage_relationship from context multitenant resource to attribute multitenant resource doesn't raise an error" do org = Org |> Ash.Changeset.new() |> Ash.create!() user = User |> Ash.Changeset.new() |> Ash.create!() diff --git a/test/support/multitenancy/domain.ex b/test/support/multitenancy/domain.ex index 52ffbc63..6c1db197 100644 --- a/test/support/multitenancy/domain.ex +++ b/test/support/multitenancy/domain.ex @@ -12,6 +12,7 @@ defmodule AshPostgres.MultitenancyTest.Domain do resource(AshPostgres.MultitenancyTest.NonMultitenantPostLink) resource(AshPostgres.MultitenancyTest.CrossTenantPostLink) resource(AshPostgres.MultitenancyTest.CompositeKeyPost) + resource(AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink) end authorization do diff --git a/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex b/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex new file mode 100644 index 00000000..4dff4d41 --- /dev/null +++ b/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex @@ -0,0 +1,52 @@ +defmodule AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.MultitenancyTest.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "non_multitenant_post_multitenant_links" + repo AshPostgres.TestRepo + + references do + reference :source, on_delete: :delete, index?: true + reference :dest, on_delete: :delete, index?: true + end + end + + multitenancy do + strategy(:context) + end + + actions do + default_accept(:*) + + defaults([:create, :read, :update, :destroy]) + end + + identities do + identity(:unique_link, [:source_id, :dest_id]) + end + + attributes do + uuid_primary_key :id + + attribute :state, :atom do + public?(true) + constraints(one_of: [:active, :archived]) + default(:active) + end + end + + relationships do + belongs_to :source, AshPostgres.MultitenancyTest.Post do + public? true + allow_nil? false + end + + belongs_to :dest, AshPostgres.Test.Post do + public? true + allow_nil? false + end + end +end diff --git a/test/support/multitenancy/resources/post.ex b/test/support/multitenancy/resources/post.ex index e097fffb..b3358361 100644 --- a/test/support/multitenancy/resources/post.ex +++ b/test/support/multitenancy/resources/post.ex @@ -59,12 +59,23 @@ defmodule AshPostgres.MultitenancyTest.Post do # has_many(:non_multitenant_post_links, AshPostgres.MultitenancyTest.NonMultitenantPostLink) + has_many :non_multitenant_post_multitenant_links, AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink do + destination_attribute :source_id + end + many_to_many :linked_non_multitenant_posts, AshPostgres.Test.Post do through(AshPostgres.MultitenancyTest.NonMultitenantPostLink) join_relationship(:non_multitenant_post_links) source_attribute_on_join_resource(:source_id) destination_attribute_on_join_resource(:dest_id) end + + many_to_many :linked_non_multitenant_posts_through_multitenant_link, AshPostgres.Test.Post do + through(AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink) + join_relationship(:non_multitenant_post_links_through_multitenant_link) + source_attribute_on_join_resource(:source_id) + destination_attribute_on_join_resource(:dest_id) + end end calculations do