Skip to content

Commit

Permalink
Merge branch 'master' into graphql-1.8
Browse files Browse the repository at this point in the history
  • Loading branch information
CyborgMaster committed May 9, 2018
2 parents 3209a7f + a48e4ed commit b1335ed
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 13 deletions.
38 changes: 28 additions & 10 deletions lib/graphql/preload/instrument.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,36 @@ def instrument(_type, field)
end
end

private def preload(record, associations)
raise TypeError, "Expected #{associations} to be a Symbol, not a String" if associations.is_a?(String)
return preload_single_association(record, associations) if associations.is_a?(Symbol)
private def preload(record, associations, scope)
if associations.is_a?(String)
raise TypeError, "Expected #{associations} to be a Symbol, not a String"
elsif associations.is_a?(Symbol)
return preload_single_association(record, associations, scope)
end

promises = []

Array.wrap(associations).each do |association|
case association
when Symbol
promises << preload_single_association(record, association)
promises << preload_single_association(record, association, scope)
when Array
association.each do |sub_association|
promises << preload(record, sub_association)
promises << preload(record, sub_association, scope)
end
when Hash
association.each do |sub_association, nested_association|
promises << preload_single_association(record, sub_association).then do
promises << preload_single_association(record, sub_association,
scope).then do
associated_records = record.public_send(sub_association)

case associated_records
when ActiveRecord::Base
preload(associated_records, nested_association)
preload(associated_records, nested_association, scope)
else
Promise.all(
Array.wrap(associated_records).map do |associated_record|
preload(associated_record, nested_association)
preload(associated_record, nested_association, scope)
end
)
end
Expand All @@ -60,8 +64,22 @@ def instrument(_type, field)
Promise.all(promises)
end

private def preload_single_association(record, association)
GraphQL::Preload::Loader.for(record.class, association).load(record)
private def preload_single_association(record, association, scope)
# We would like to pass the `scope` (which is an
# `ActiveRecord::Relation`), directly into `Loader.for`, because that is
# what is needed for `Preloader.new`. However, because the scope is
# created for each parent record, they are different objects and
# therefore would return different loaders, breaking batching.
# Therefore, we pass in `scope.to_sql`, which is the same for all the
# scopes and set the `scope` using an accessor. So the actual scope
# object used will be the last one, which shouldn't make any difference,
# beacuse even though they are different objects, they are all
# equivalent.

loader = GraphQL::Preload::Loader.for(record.class, association,
scope.try(:to_sql))
loader.scope = scope
loader.load(record)
end
end
end
Expand Down
17 changes: 14 additions & 3 deletions lib/graphql/preload/loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ module Preload
# Preloads ActiveRecord::Associations when called from the Preload::Instrument
class Loader < GraphQL::Batch::Loader
attr_reader :association, :model
attr_accessor :scope

def cache_key(record)
record.object_id
end

def initialize(model, association)
def initialize(model, association, _scope_sql)
@association = association
@model = model

Expand Down Expand Up @@ -36,9 +37,19 @@ def perform(records)
private def preload_association(records)
if ((ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 1) ||
ActiveRecord::VERSION::MAJOR > 4)
ActiveRecord::Associations::Preloader.new.preload(records, association)
ActiveRecord::Associations::Preloader.new.preload(records, association,
preload_scope)
else
ActiveRecord::Associations::Preloader.new(records, association).run
ActiveRecord::Associations::Preloader.new(records, association,
preload_scope).run
end
end

private def preload_scope
if scope.try(:klass) == model.reflect_on_association(association).klass
scope
else
nil
end
end

Expand Down

0 comments on commit b1335ed

Please sign in to comment.