Limit loading relationship of grandchild to grandparent

If I have relations like this:

blogs has_many posts
posts many_to_many users
blogs many_to_many users

How can I limit loading posts of users to only one blog?
blog (id 80) → users → posts of blog (id 80)

By default it loads all posts of users in every blog.
blog (id 80) → users → posts of all blogs

In Ecto, I can do this

Repo.get!(Blog, 80)
|> Repo.preload([
   users: [posts: from(p in Post, where: p.blog_id == 80)]
])
Api.get!(Blog, 80)
|> Api.load(users: [posts: Ash.Query.filter(blog_id == ^80)])
1 Like

Can I use Ash.Query.filter for loading relationships in ash_graphql or ash_json_api declaratively?

listBlogs {
 id
 users {
   id
   posts {
     id # posts in grandparent blog only?
   }
 }

If I load a list of Blogs with a custom read action, how would I pass the blog_id to filter posts?

read :list_blog_user_post do
  prepare build(load: [
    users: [
      posts: Ash.Query.filter(blog_id == ^blog_id) # ???
    ]
  ])
end

There is not really a good way to accomplish this if I’m being honest. It’s not a pattern that would be easily generalizable to support all of the relationship features that Ash has. You’d potentially be able to accomplish this with a calculation on the parent object (blog) that produces a list of users and for each their posts in the parent blog. I.e

defmodule YourApp.Types.UserWithPosts do
  use Ash.Resource, data_layer: :embedded

  attributes do
    attribute :user, :struct do
      constraints [instance_of: User]
      allow_nil? false
    end

    attribute :posts, {:array, :struct} do
      constraints [items: [instance_of: Post]]
      allow_nil? false
    end
  end 
end

# in blog
calculate :users_and_posts, {:array, YourApp.Types.UserWithPosts}, YourCalculation

Where YourCalculation is a module that produces a list of those embedded types.

1 Like