Last active
June 28, 2020 12:18
-
-
Save RStankov/48070003a31d71a66f57a237e27d5865 to your computer and use it in GitHub Desktop.
This file contains 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
# Note(rstankov): | |
# | |
# Preload associations | |
# | |
# **Don't use for `connection`s, only for `field` attributes** | |
# | |
# supported definitions: | |
# - [x] :assoc | |
# - [x] { assoc: :assoc } | |
# - [x] { assoc: { assoc: :assoc } } | |
# - [x] [:assoc, :assoc] | |
# - [x] { assoc: [ :assoc, :assoc ] } | |
# | |
# not supported: (because their response is not obvious) | |
# - [ ] [ :assoc, { assoc: :assoc } } | |
# - [ ] [ { assoc: :assoc }, { assoc: :assoc } } | |
class Graph::Resolvers::AssociationResolver < GraphQL::Function | |
def initialize(preload, &block) | |
@preload = normalize_preloads(preload) | |
@handler = block || DefaultHandler | |
end | |
def call(obj, _args = nil, _ctx = nil) | |
return unless obj.present? | |
next_step preload.dup, obj, obj | |
end | |
private | |
def next_step(items, obj, previous_assoc) | |
preload_associations(previous_assoc, items.shift).then do |assoc| | |
if items.empty? | |
handle obj, assoc | |
else | |
next_step items, obj, assoc | |
end | |
end | |
end | |
def preload_associations(model, associations) | |
if associations.is_a? Array | |
Promise.all(associations.map { |name| preload_association(model, name) }) | |
else | |
preload_association(model, associations) | |
end | |
end | |
def preload_association(model, association_name) | |
AssociationLoader.for(model.class, association_name).load(model) | |
end | |
def normalize_preloads(preloads) | |
if preloads.is_a? Hash | |
keys = preloads.keys | |
raise NotSupported, 'only one nested association supported currently' unless keys.size == 1 | |
first_key = keys.first | |
[first_key] + normalize_preloads(preloads[first_key]) | |
else | |
[preloads] | |
end | |
end | |
def handle(obj, assoc) | |
if handler.arity == 2 | |
handler.call assoc, obj | |
else | |
handler.call assoc | |
end | |
end | |
attr_reader :preload, :handler | |
class NotSupported < StandardError; end | |
module DefaultHandler | |
extend self | |
def arity | |
1 | |
end | |
def call(assoc) | |
assoc | |
end | |
end | |
class AssociationLoader < GraphQL::Batch::Loader | |
def initialize(model, association) | |
@model = model | |
@association = association | |
end | |
def load(record) | |
raise TypeError, "#{ @model } loader can't load association for #{ record.class }" unless record.is_a?(@model) | |
return Promise.resolve(read_association(record)) if association_loaded?(record) | |
super | |
end | |
# 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, association) | |
records.each do |record| | |
fulfill(record, read_association(record)) | |
end | |
end | |
private | |
attr_reader :model, :association | |
def read_association(record) | |
record.public_send(association) | |
end | |
def association_loaded?(record) | |
record.association(association).loaded? | |
end | |
end | |
end |
This file contains 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
Graph::Types::PostType = GraphQL::ObjectType.define do | |
# ... other stuff | |
# belogns to | |
field :user, Graph::Types::UserType, function: Graph::Resolvers::AssociationResolver.new(:user) | |
# has many | |
field :topics, Graph::Types::UpcomingPageType, function: Graph::Resolvers::AssociationResolver.new(:topics) | |
# belongs_to -> belongs_to -> custom resolver | |
field :shortcode, !types.String, function: Graph::Resolvers::AssociationResolver.new(product: :primary_link) do |link| | |
ShortcodeExtract.call(link) | |
end | |
# this can be: | |
# Graph::Resolvers::AssociationResolver.new({ product: :primary_link}, Shortcode) | |
# Unfortunally, `AssociationResolver` is not usable with scopes and Relay `connection` interface | |
connection :alternatives, Graph::Types::PostType.connection_type do | |
resolve ->(post, _args, _ctx) { post.alternatives.by_credible_votes } | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment