diff --git a/README.md b/README.md index 844fb2b..449098e 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,12 @@ The loader class can be used from the resolve proc for a graphql field by callin resolve -> (obj, args, context) { RecordLoader.for(Product).load(args["id"]) } ``` +Although this library doesn't have a dependency on active record, +the [examples directory](examples) has record and association loaders +for active record which handles edge cases like type casting ids +and overriding GraphQL::Batch::Loader#cache_key to load associations +on records with the same id. + ### Promises GraphQL::Batch::Loader#load returns a Promise using the [promise.rb gem](https://rubygems.org/gems/promise.rb) to provide a promise based API, so you can transform the query results using `.then` diff --git a/examples/association_loader.rb b/examples/association_loader.rb new file mode 100644 index 0000000..8eec98a --- /dev/null +++ b/examples/association_loader.rb @@ -0,0 +1,48 @@ +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 diff --git a/examples/record_loader.rb b/examples/record_loader.rb new file mode 100644 index 0000000..110f0f4 --- /dev/null +++ b/examples/record_loader.rb @@ -0,0 +1,28 @@ +class RecordLoader < GraphQL::Batch::Loader + def initialize(model, column: model.primary_key, where: nil) + @model = model + @column = column.to_s + @column_type = model.type_for_attribute(@column) + @where = where + end + + def load(key) + super(@column_type.cast(key)) + end + + def perform(keys) + query(keys).each do |record| + value = @column_type.cast(record.public_send(@column)) + fulfill(value, record) + end + keys.each { |key| fulfill(key, nil) unless fulfilled?(key) } + end + + private + + def query(keys) + scope = @model + scope = scope.where(@where) if @where + scope.where(@column => keys) + end +end