Skip to content

Commit

Permalink
Add Active Record AssociationLoader and RecordLoader examples (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanahsmith committed Aug 15, 2017
1 parent ae230d5 commit 058213b
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 0 deletions.
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -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`
Expand Down
48 changes: 48 additions & 0 deletions 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
28 changes: 28 additions & 0 deletions 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

0 comments on commit 058213b

Please sign in to comment.