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 с другими возможностями и попробуйте в своем проекте!
