使用GraphQL Ruby进行批量加载具有组合主键的记录

简述

我将尝试批量加载复合主键记录。

我所在的团队几年前引入了GraphQL,因此使用了graphql-batch。但是我认为现在普遍使用graphql-ruby的dataloder,所以我会尝试在两者上都进行实现。

假设

假设有以下的表格,

考虑以下情景,查找视图器是否在以下查询中关注了用户。

query ($id: ID!) {
  user(id: $id) { isFollowedByViewer }
}

GraphQL批处理的情况下

将其实现为Loaders::MultiColumnRecordLoader。

module Loaders
  class MultiColumnRecordLoader < GraphQL::Batch::Loader
    def initialize(model, columns:)
      @model = model
      @columns = columns
    end

    def perform(keys)
      conditions = keys.map {|key| @columns.zip(key).to_h }

      scoped = conditions.inject(@model.none) {|s, condition| s.or(@model.where(condition)) }

      scoped.each do |record|
        key = @columns.map {|column| record.public_send(column) }

        fulfill(key, record)
      end

      keys.each do |key|
        next if fulfilled?(key)

        fulfill(key, nil)
      end
    end
  end
end

呼叫将是以下的方式

module Types
  class UserType < Types::BaseObject
    field :is_followed_by_viewer, Boolean, resolver_method: :followed_by_viewer?

    def followed_by_viewer?
      return false unless viewer

      loader = Loaders::MultiColumnRecordLoader.for(Follow, columns: %i[follower_id followee_id])

      loader([viewer.id, object.id]).then(&:present?)
    end
  end
end

GraphQL Ruby – Dataloader 的情况

实现为MultiColumnRecordSource。

module Sources
  class MultiColumnRecordSource < GraphQL::Dataloader::Source
    def initialize(model, columns:)
      @model = model
      @columns = columns
    end

    def fetch(keys)
      conditions = keys.map {|key| @columns.zip(key).to_h }

      scoped = conditions.inject(@model.none) {|s, condition| s.or(@model.where(condition)) }

      mapping = scoped.inject({}) do |record, m|
        key = @columns.map {|column| record.public_send(column) }

        m[key] = record
      end

      keys.map {|key| mapping.fetch(key, nil) }
    end
  end
end

呼叫可能如下所示。

module Types
  class UserType < Types::BaseObject
    field :is_followed_by_viewer, Boolean, resolver_method: :followed_by_viewer?

    def followed_by_viewer?
      return false unless viewer

      loader = dataloader.with(Sources::MultiColumnRecordSource, Follow, %i[follower_id followee_id])

      follow = loader.load([viewer.id, object.id])

      follow.present?
    end
  end
end

个人感受

我更喜欢GraphQL Ruby中的Dataloader。

bannerAds