N1Loader разработан для легкого избежания N+1 проблемы
любого типа. К счатью, гем очень легко интегрировать в GraphQL
API. Без дальнейших отлагательств, давайте рассмотрим простой, но самодостаточный пример.
# Добавляем N1Loader с интеграцией ArLazyPreload
require "n1_loader/ar_lazy_preload"
require 'graphql'
# Создаем SQLite таблицы в памяти, не относится к библиотеке.
require_relative 'context/setup_database'
# ArLazyPreload требует Rails приложение. Этот код необходим, чтобы избежать этого.
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
# Запускаем ArLazyPreload глобально
ArLazyPreload.config.auto_preload = true
# Или используем preload_associations_lazily когда загружаем объекты из базы данных
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
# Исполняем запрос без N+1!
p Schema.execute(query_string)['data']
На этом волшебство N1Loader не заканчивается, так как библиотека так же поддерживает аргументы, которые могут приходить с запросом. И все это без N+1!
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
# Нет N+1 и больше не будет!
p Schema.execute(query_string, variables: {from: 3.days.ago, to: 1.day.ago})['data']
Описывайте загрузку данные единожды - и используйте в любой части приложения без N+1!
Посмотрите страницу N1Loader с другими возможностями и попробуйте в своем проекте!