How to load a nested relationship with a tenant?

I'm a beginner with Ash and essentially I'd like to lookup an organization while loading some relationships, something like this:
Sites.Organization
|> Ash.Query.load(user_roles: [:user])
|> Ash.read!()
Sites.Organization
|> Ash.Query.load(user_roles: [:user])
|> Ash.read!()
(I only really need a single organization at the moment but I suppose the question is more general.) However to load the user roles I have to specify the tenant (which is the organization itself) and I get this error:
Queries against the Guilds.Sites.OrganizationUserRole resource require a tenant to be specified
at user_roles
Queries against the Guilds.Sites.OrganizationUserRole resource require a tenant to be specified
at user_roles
How can I load the organization while also loading the relationships that needs the organization itself? The organization and users live in the "public" schema while the user roles lives in a schema specified by the organization id. The organization is specified with:
multitenancy do
strategy :attribute
attribute :id
global? true
end

relationships do
has_many :user_roles, Guilds.Sites.OrganizationUserRole
end
multitenancy do
strategy :attribute
attribute :id
global? true
end

relationships do
has_many :user_roles, Guilds.Sites.OrganizationUserRole
end
And the user role:
multitenancy do
strategy :context
end

relationships do
belongs_to :organization, Guilds.Sites.Organization do
primary_key? true
allow_nil? false
end

belongs_to :user, Guilds.Accounts.User do
primary_key? true
allow_nil? false
end
end
multitenancy do
strategy :context
end

relationships do
belongs_to :organization, Guilds.Sites.Organization do
primary_key? true
allow_nil? false
end

belongs_to :user, Guilds.Accounts.User do
primary_key? true
allow_nil? false
end
end
15 Replies
jonas_h
jonas_hOP•4mo ago
I tried to work around it by adding a many_to_many in organization:
many_to_many :users, Guilds.Accounts.User do
join_relationship :user_roles
destination_attribute_on_join_resource :user_id
end
many_to_many :users, Guilds.Accounts.User do
join_relationship :user_roles
destination_attribute_on_join_resource :user_id
end
I've verified that the data is created correctly in the db (with user roles in the organization schema and the other in public) but if I try to load it the user is still nil but the organization works:
Guilds.Sites.Organization |> Ash.Query.load([:users]) |> Ash.read!()
Guilds.Sites.Organization |> Ash.Query.load([:users]) |> Ash.read!()
I haven't added any explicit multitenancy code to the user but I'm unsure how to do that and as I said the creation worked fine.
ZachDaniel
ZachDaniel•4mo ago
We've needed a feature for this for a while when loading relationships where you can say that the tenant is the source of the load. Please open a feature proposal for it But for now what you'd want to do is figure out the tenant for the org before loading the tenanted relationships You can load after the fact with 'Ash.load'
jonas_h
jonas_hOP•4mo ago
I'm still having trouble finding the user reference (going from tenant to no tenant). Is that how it's supposed to be setup or does the user require a multitenancy description even though it lives in the public schema?
ZachDaniel
ZachDaniel•4mo ago
When you say 'finding the user reference' I'm not sure what you mean sorry You can set a tenant when reading an untenated resource i.e
user
|> Ash.load!([:foo, :bar], tenant: tenant)
user
|> Ash.load!([:foo, :bar], tenant: tenant)
It may be that the user is coming back as nil because of your policies Try this:
Guilds.Sites.Organization |> Ash.Query.load([:users]) |> Ash.read!(authorize?: false)
Guilds.Sites.Organization |> Ash.Query.load([:users]) |> Ash.read!(authorize?: false)
jonas_h
jonas_hOP•4mo ago
Ooh, yeah I was missing authorize?: false! Thank you!
ZachDaniel
ZachDaniel•4mo ago
Keep in mind that for your real usage you typically want to be actually modifying your policies to fit For admin/testing etc. all good though
jonas_h
jonas_hOP•4mo ago
I would've expected a policy error when using load like this. I read in the book that read works differently but still a bit confused to be honest
Sites.OrganizationUserRole
|> Ash.Query.load([:user, :organization])
|> Ash.read!(tenant: org, authorize?: false)
Sites.OrganizationUserRole
|> Ash.Query.load([:user, :organization])
|> Ash.read!(tenant: org, authorize?: false)
I used this code
ZachDaniel
ZachDaniel•4mo ago
Yep, so read policies by default filter to only show data that you can see as the requester this is a security measure that prevents leaking internals about your data
jonas_h
jonas_hOP•4mo ago
Oh right, to prevent enumeration attacks?
ZachDaniel
ZachDaniel•4mo ago
For example lets say I knew that the user zach@daniel.com was in your system, and I wanted to see what state they live in, if I do /users?filter[email]=zach@daniel.com&filter[state]=FL etc. I could tell from 403 vs 404 where you live and yeah I typed all that up but you already knew it 😜
jonas_h
jonas_hOP•4mo ago
Heh
ZachDaniel
ZachDaniel•4mo ago
So yeah we just take a cautious approach there but you can change it w/ access_type access_type :strict and access_type :runtime (almost never use the latter)
jonas_h
jonas_hOP•4mo ago
In this particular case I'd expect that the whole UserRole would be filtered (as it doesn't make sense without a user)
ZachDaniel
ZachDaniel•4mo ago
Thats totally up to your policy setup i.e perhaps your user roles should have policies indicating who can/can't read them etc.
jonas_h
jonas_hOP•4mo ago
Alright, that's fair.

Did you find this page helpful?