khiav223577 / deep_pluck

Allow you to pluck attributes from nested associations without loading a bunch of records.
MIT License
460 stars 14 forks source link

Could this be used with GraphQL? #37

Open vfonic opened 4 years ago

vfonic commented 4 years ago

GraphQL generally works in a way that you can query exactly the fields (columns) that you want from the database like this:

{
  users {
    name
    posts {
      title
    }
  }
}

There's a very popular GraphQL gem for ruby: https://github.com/rmosolgo/graphql-ruby

Currently, the best solution that I've sees is using batch querying. It would looks something like this in reality:

users = User.all
posts = Post.find(user_id: users.map(&:id))

Perhaps this could be optimized using deep_pluck? It would already be a huge improvement if it could pluck only requested fields:

users = User.pluck(:id, :name)
posts = Post.where(user_id: users.map(&:id)).pluck(:title)

Note that we also need to fetch user.id even though that's not in the query.

Any ideas how this could be achieved? Could this be something that could be done on the graphql-ruby gem perhaps?

khiav223577 commented 4 years ago

It will be difficult to use deep_pluck with GraphQL since it have to know all selected fields including nested child fields before querying. As far as I know, we can only get one-level child fields by using lookahead feature. (I found the feature in https://github.com/rmosolgo/graphql-ruby/issues/2196 )

One possible way that may works is adding a wrapper type in the top of the types, and query all the data in it. Then use hash_key (I found the feature in https://github.com/rmosolgo/graphql-ruby/issues/107) to lookup the value from the hash via the key.

For example: (I've not tested it yet)

module Types
  class UsersType < Types::BaseObject
    field :users, [UserType]

    def users
      batch do |ids| # TODO: replace batch method by your batch querying tool.
        User.where(id: ids).deep_pluck(:name, posts: [:title, :content])

        # Or select all columns of the model:
        # User.where(id: ids).deep_pluck(*User.column_names, posts: Post.column_names)
      end
    end
  end
end
module Types
  class UserType < Types::BaseObject
    field :name, hash_key: 'name'
    field :posts, [PostType], hash_key: :posts
  end
end
module Types
  class PostType < Types::BaseObject
    field :title, hash_key: 'title'
    field :content, hash_key: 'content'
  end
end

It will get a huge performance improvement even if we select all columns that are not to be used, in that we only load the raw data and do not instantiate the models.

vfonic commented 4 years ago

It will be difficult to use deep_pluck with GraphQL since it have to know all selected fields including nested child fields before querying. As far as I know, we can only get one-level child fields by using lookahead feature. (I found the feature in rmosolgo/graphql-ruby#2196 )

It's actually possible to get all the nested child fields using lookahead. :)

Hey @rmosolgo, do you think any of these would make sense to look further into? It doesn't have to become a standard part of 'graphql-ruby', but perhaps an extension for active record that would allow for easy performance boost (and avoiding N+1 or even multiple (batch) queries)? Do you see any downsides to this approach?

rmosolgo commented 4 years ago

I think it would be an awesome thing to look into! It's always been "theoretically possible" but I haven't heard of anyone who actually worked it out. Please share your results if you get something working!

khiav223577 commented 4 years ago

It will be difficult to use deep_pluck with GraphQL since it have to know all selected fields including nested child fields before querying. As far as I know, we can only get one-level child fields by using lookahead feature. (I found the feature in rmosolgo/graphql-ruby#2196 )

It's actually possible to get all the nested child fields using lookahead. :)

@vfonic I took some time to learn graphql-ruby, and finally created a graphql project that uses deep_pluck to query data. It's a very simple project while it shows it is possible to use deep_pluck with graphql-ruby. See: https://github.com/khiav223577/rails-graphql-pluck-test/pull/2

I think it would be an awesome thing to look into! It's always been "theoretically possible" but I haven't heard of anyone who actually worked it out. Please share your results if you get something working!

@rmosolgo You might also be interested in it :)

vfonic commented 4 years ago

Daaaamn! At first glance, this looks amazing! Thank you, @khiav223577!

When I find time, I'll try to add this to my project and let you know how it goes. I'm currently using custom method that also relies on lookahead, but queries for all the fields. This seems like a better solution.