N1Loader is designed to provide a simple way for avoiding N+1 issues
of any kind. Gladly, it's super easy to integrate with your GraphQL
API. Without further delay, let's look at a simple but yet detailed
example.
# Add N1Loader with ArLazyPreload integration require "n1_loader/ar_lazy_preload" require 'graphql' # Setup SQLite tables in memory. That is irrelevant to the example. require_relative 'context/setup_database' # ArLazyPreload requires Rails application. This is needed to avoid that. require_relative 'context/setup_ar_lazy' class User < ActiveRecord::Base has_many :payments n1_optimized :payments_total do |users| # Fetch payments for a group of users we will preload in a single query total_per_user = Payment.group(:user_id).where(user: users).sum(:amount).tap { |h| h.default = 0 } users.each do |user| total = total_per_user[user.id] # No promises here, simply add a value for to a user. fulfill(user, total) end end end class Payment < ActiveRecord::Base belongs_to :user validates :amount, presence: true end # Enable ArLazyPreload globally ArLazyPreload.config.auto_preload = true # Or use +preload_associations_lazily+ when loading objects from database class UserType < GraphQL::Schema::Object field :payments_total, Integer end class QueryType < GraphQL::Schema::Object field :users, [UserType] def users User.all end end class Schema < GraphQL::Schema query QueryType end query_string = <<~GQL { users { paymentsTotal } } GQL # No N+1. And never will be! p Schema.execute(query_string)['data']
N1Loader supports arguments that you can pass through GraphQL API. There will no N+1 still.
class User < ActiveRecord::Base n1_optimized :payments_total do argument :from argument :to def perform(users) total_per_user = Payment .group(:user_id) .where(created_at: from..to) .where(user: users) .sum(:amount) .tap { |h| h.default = 0 } users.each do |user| total = total_per_user[user.id] fulfill(user, total) end end end end class UserType < GraphQL::Schema::Object field :payments_total, Integer do argument :from, Time argument :to, Time end end query_string = <<~GQL { users { paymentsTotal } } GQL # No N+1. And never will be! p Schema.execute(query_string, variables: {from: 3.days.ago, to: 1.day.ago})['data']
Define loaders once - use it everywhere without N+1.
Check N1Loader for more features and give it a try in your projects.