Setting Tenant when using Context Multitenancy

So I have configure my resources to use context multitenancy (Postgres Schemas). Migrations are working fine and creating an org creates an org schema as well. My question is how can I set the tenant, assuming am getting the tenant from the subdomain, which can the be used in Ash queries? I have seen Ash.set_tenant/1 which takes a map as argument, what does the map look like? How is it different from Ash.Query.set_tenant/2? An also there what's called Process Context is Ash, what is it and assuming it's a data structure stored in the Process dictionary, how does it look like? Examples or pseudo code will help. Hopefully my questions are making sense 😁
14 Replies
ZachDaniel
ZachDaniel3y ago
You essentially have two options. 1. Provide the tenant when creating changesets (this is what I typically advise, but its a matter of preference). That looks like this:
Ash.Changeset.for_create(Resource, :create, .., tenant: "tenant_name")
Ash.Query.for_read(Resource, :read, .., tenant: "tenant_name")
Resource.code_interface_function(tenant: "tenant_name"
Ash.Changeset.for_create(Resource, :create, .., tenant: "tenant_name")
Ash.Query.for_read(Resource, :read, .., tenant: "tenant_name")
Resource.code_interface_function(tenant: "tenant_name"
2. Set the tenant into process context using Ash.set_tenant/1
Ash.set_tenant("tenant_name")
Ash.Changeset.for_create(Resource, :create, ..)
Ash.Query.for_read(Resource, :read, ..)
Resource.code_interface_function()
Ash.set_tenant("tenant_name")
Ash.Changeset.for_create(Resource, :create, ..)
Ash.Query.for_read(Resource, :read, ..)
Resource.code_interface_function()
The main thing about using the process dictionary is that you'll have to make sure you call Ash.set_tenant("tenant_name") in any processes you start
Jmanda
JmandaOP3y ago
Thank you @Zach Daniel
ZachDaniel
ZachDaniel3y ago
My pleasure 🙂
zimt28
zimt283y ago
If I remember correctly set_tenant/1 expects a map, even though it should be a string It still works but ElixirLS will complain
ZachDaniel
ZachDaniel3y ago
...weird
zimt28
zimt283y ago
I think the typespec is wrong, but I cannot check right now
ZachDaniel
ZachDaniel3y ago
fixed 🙂
zimt28
zimt283y ago
But it would still be nice to have a callback somewhere that allows passing an actual tenant and converting it to a string in the callback
ZachDaniel
ZachDaniel3y ago
Yeah, agreed that would be nice That would be a good first issue actually I'll make a ticket for it soon It would really just be a protocol you can implement, i.e
defimpl Ash.ToTenant do
def to_tenant(%Org{short_name: name}), do: name
end
defimpl Ash.ToTenant do
def to_tenant(%Org{short_name: name}), do: name
end
Jmanda
JmandaOP3y ago
What's the equivalent of Triplex.exists?("tenant1") and Triplex.to_prefix(tenant) in Ash? And also if my resource is configured likes this:
postgres do
...
manage_tenant do
template ["org_", :id]
end
end
postgres do
...
manage_tenant do
template ["org_", :id]
end
end
it would nice if calling Ash.set_tenant/1 automatically adds the set prefix, for example Ash.set_tenant("tenant_1") results in {:tenant, org_tenant_1} instead of {:tenant, tenant_1}
ZachDaniel
ZachDaniel3y ago
We don't actually have our own versions of those functions, you'll have to handroll them But to check if a tenant exists, you could either 1. lookup the org with that id, or 2. check if the schema exists using your ecto repo We could implement the to_tenant protocol automatically if you use manage_tenant in ash_postgres, which would solve for a lot of this. Just need to write the protocol.
Jmanda
JmandaOP3y ago
That would be awesome
ZachDaniel
ZachDaniel3y ago
I probably won't get to it soon though really. its a good first issue for someone else to tackle though, will write it up in a bit
Jmanda
JmandaOP3y ago
In the meantime will just "handroll" my own, shouldn't be hard I have done it before 😁

Did you find this page helpful?