-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document how one could preload has_many associations #24
Comments
I think we should make our DerivedKeyLoader builtin functionality, since is something we use to properly implement this in Shopify |
Has anyone written this up? Would love to see how you did it. |
I created for my own purposes an
class ArrayLoader < GraphQL::Batch::Loader
def initialize(model, default = nil)
@model = model
@default = default
end
def perform(keys)
queries = {}
keys.each do |key|
if key.is_a?(Array)
query_value, query_key = key
queries[query_key] = [] if queries[query_key].nil?
queries[query_key] << query_value
end
end
results_cache = {}
queries.each do |key, values|
@model.where(key => values).each { |record|
results_cache[[record[key], key]] = [] if results_cache[[record[key], key]].nil?
results_cache[[record[key], key]] << record
}
end
results_cache.each do |key, values|
fulfill(key, values)
end
keys.each { |key| fulfill(key, @default || []) unless fulfilled?(key) }
end
end |
This is what I've hit on:
Used like so:
|
Here is what we are using, which is a bit longer to:
class AssociationLoader < GraphQL::Batch::Loader
def self.validate(model, association_name)
new(model, association_name)
nil
end
def initialize(model, association_name)
@model = model
@association_name = association_name
validate
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)
preload_association(records)
records.each { |record| fulfill(record, read_association(record)) }
end
private
def validate
unless @model.reflect_on_association(@association_name)
raise ArgumentError, "No association #{@association_name} on #{@model}"
end
end
def preload_association(records)
::ActiveRecord::Associations::Preloader.new.preload(records, @association_name)
end
def read_association(record)
record.public_send(@association_name)
end
def association_loaded?(record)
record.association(@association_name).loaded?
end
end |
something along these lines would be a wonderful addition to either the documentation or stdlib of graphql-batch. |
If it helps, I wrote up a gist that shows how you could combine a field :comments, !types[!CommentType] do
preload comments: :author
resolve -> (post, args, ctx) { post.comments }
end It looks a little bit nicer than stacking a bunch of https://gist.github.com/theorygeek/a1a59a2bf9c59e4b3706ac68d12c8434 |
@theorygeek above gist worked wonderfully for me. Is this merged into gem? |
@theorygeek that gist was also exactly what I was looking for, thanks! 👍 It would certainly be nice to have this merged into the gem, or at least some documentation written up for how to best batch association loading (I came here after spelunking into the tests for this gem to see how the test suite was doing it). |
@theorygeek - Thanks!!! I spent most of the day struggling with my crappy code. Finally I ripped out may crappy code and replaced if with your awesome code. If your code is too specific for this gem, I suggest a new gem, call it |
Hey everyone - I took @theorygeek's excellent work and turned it into a gem. Hopefully you find it useful! |
@etripier - Awesome! |
@etripier Nice work! 🎉 |
@timscott @theorygeek Awesome just what I was looking for 🤗 👏🏻 this should be added to the GEM |
@dylanahsmith's snippet also handled the cache id smartly, IIUC, isn't that needed in the gem? |
@riffraff Good point! I went ahead and fixed that in the latest release. |
Hey guys, this might seem very dumb but ...
The following extra light class seems to be working: # implementation
class AssociationLoader < GraphQL::Batch::Loader
def initialize(association_name)
@association_name = association_name
end
private
def perform(records)
::ActiveRecord::Associations::Preloader.new.preload(records, @association_name)
records.each { |record| fulfill(record, record.public_send(@association_name)) }
end
end
# usage
field :posts, !types[Types::Post] do
resolve -> (record, _args, _ctx) { AssociationLoader.for(:posts).load(record) }
end Am I missing something? (I am very new to GraphQL) Following @dylanahsmith 's class in comparison to the above class:
|
Sounds like the questions you're asking lead naturally to the expanded version but with stylistic variations. What motivates the analysis? |
I was wondering if my code was breaking something I could not "see" from my basic examples/tests. |
Maybe you are right. I think that validate class method might only matter if you want to use it to validate field preloads when declaring them at initialization time for fields, like in https://github.com/xuorig/graphql-active_record_batcher.
The model is used for grouping so that you don't end up grouping together loads for the same association name on different models. |
There was some discussion about how one might preload
has_many
associations in#graphql
.It might be interesting to share some of our
AssociationLoader
logic or writing a quick tutorial to achieve this in theREADME
.The text was updated successfully, but these errors were encountered: