Created
March 13, 2022 21:10
-
-
Save RStankov/aabc1483de45052520d67f7afe82d7b2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
# Note(rstankov): | |
# | |
# **Don't use for `connection`s, only for `field` attributes** | |
# | |
# Preload associations. | |
# | |
# Supports all preload formats from [ActiveRecord::Base.includes](https://api.rubyonrails.org/v5.2.4/classes/ActiveRecord/QueryMethods.html#method-i-includes). | |
# | |
# Default handler returns | |
# | |
# - preload(:assoc) => model.assoc | |
# - preload([:assoc1, assoc2]) => model.assoc1 | |
# - preload(assoc1: :nested1, assoc2: :nested2) => model.assoc1 | |
# | |
module Graph::AssociationResolver | |
extend self | |
def call(preload:, type:, null:, handler: nil) | |
resolver_class = Class.new(Resolver) | |
resolver_class.type type, null: null | |
resolver_class.define_method(:preload) { preload } | |
resolver_class.define_method(:handler) { handler } if handler.present? | |
resolver_class | |
end | |
class Resolver < Resolvers::Base | |
def resolve | |
return if object.blank? | |
AssociationLoader.for(preload).load(object).then do |loaded_association| | |
if handler.arity == 2 | |
handler.call loaded_association, object | |
else | |
handler.call loaded_association | |
end | |
end | |
end | |
def preload | |
raise NotImplementedError | |
end | |
def handler | |
DefaultHandler | |
end | |
end | |
module DefaultHandler | |
extend self | |
def arity | |
1 | |
end | |
def call(loaded_association) | |
loaded_association | |
end | |
end | |
class AssociationLoader < GraphQL::Batch::Loader | |
def initialize(preload) | |
@preload = preload | |
end | |
def load(record) | |
return Promise.resolve(read_association(record, preload)) if association_loaded?(record, preload) | |
super | |
end | |
# NOTE(rstankov): We want to load the associations on all records, even if they have the same id | |
def cache_key(record) | |
record.object_id | |
end | |
def perform(records) | |
::ActiveRecord::Associations::Preloader.new.preload(records, preload) | |
records.each do |record| | |
fulfill(record, read_association(record, preload)) | |
end | |
end | |
private | |
attr_reader :preload | |
def read_association(record, association_name) | |
case association_name | |
when Array then read_association(record, association_name.first) | |
when Hash then read_association(record, association_name.keys.first) | |
else record.public_send(association_name) | |
end | |
end | |
def association_loaded?(record, association_name) | |
case association_name | |
when Array then association_name.all? { |name| association_loaded?(record, name) } | |
when Hash | |
association_name.all? do |(name, nested_associations)| | |
association_loaded?(record, name) && association_loaded?(read_association(record, name), nested_associations) | |
end | |
else record.association(association_name).loaded? | |
end | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
module Types | |
class BaseRecord < Types::BaseObject | |
field :id, ID, null: false | |
def self.association(name, type, null: false, preload: nil, deprecation_reason: nil, &block) | |
field( | |
name, | |
resolver: ::Graph::AssociationResolver.call( | |
preload: preload || name, | |
type: type, | |
null: null, | |
handler: block, | |
), | |
deprecation_reason: deprecation_reason, | |
) | |
end | |
def self.connection(name, type, **options) | |
field name, type.connection_type, null: false, **options | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment