GraphQL::Sources

GraphQL::Sources
is a set of predefined dataloader classes build to avoid common n-plus-one issues in a GraphQL schema with Ruby. It supports loading has_one
, has_many
, belongs_to
, has_and_belongs_to_many
, has_one_attached
and has_many_attached
associations.
Recent versions of graphql-ruby include built-in support for loading records and associations via the dataload_record and dataload_association methods. These serve as a suitable alternative to some of the methods of loading provided by this library.
Installation
Install the gem and add to the application's Gemfile by executing:
$ bundle add graphql-sources
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install graphql-sources
The GraphQL::Dataloader
plugin must be installed in the schema:
class AppSchema < GraphQL::Schema
use GraphQL::Dataloader
end
Usage
Loading belongs_to
/ has_many
Associations
class Purchase < ActiveRecord::Base
belongs_to :customer
end
class Customer < ActiveRecord::Base
has_many :purchases
end
class PurchaseType < GraphQL::Schema::Object
field :customer, CustomerType, null: false
def customer
dataloader
.with(GraphQL::Sources::ActiveRecordAssociation, :customer)
.load(object)
end
end
class CustomerType < GraphQL::Schema::Object
field :purchases, [PurchaseType], null: false
def purchases
dataloader
.with(GraphQL::Sources::ActiveRecordAssociation, :purchases)
.load(object)
end
end
Loading belongs_to
/ has_one
Associations
class Profile < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :profile
end
class ProfileType < GraphQL::Schema::Object
field :user, [UserType], null: false
def user
dataloader
.with(GraphQL::Sources::ActiveRecordAssociation, :user)
.load(object)
end
end
class UserType < GraphQL::Schema::Object
field :profile, [ProfileType], null: false
def profile
dataloader
.with(GraphQL::Sources::ActiveRecordAssociation, :profile)
.load(object)
end
end
Loading has_and_belongs_to_many
Associations
class Student
has_and_belongs_to_many :courses
end
class Course
has_and_belongs_to_many :students
end
class StudentType < GraphQL::Schema::Object
field :courses, [CourseType], null: false
def courses
dataloader
.with(GraphQL::Sources::ActiveRecordAssociation, :courses)
.load(object)
end
end
class CourseType < GraphQL::Schema::Object
field :students, [StudentType], null: false
def students
dataloader
.with(GraphQL::Sources::ActiveRecordAssociation, :students)
.load(object)
end
end
Loading has_one_attached
Associations
class User
has_one_attached :photo
end
class UserType < GraphQL::Schema::Object
field :avatar, AttachedType, null: false
def avatar
dataloader
.with(GraphQL::Sources::ActiveStorageHasOneAttached, :avatar)
.load(object)
end
end
Loading has_many_attached
Associations
class User
has_many_attached :photos
end
class UserType < GraphQL::Schema::Object
field :photos, [AttachedType], null: false
def photos
dataloader
.with(GraphQL::Sources::ActiveStorageHasManyAttached, :photos)
.load(object)
end
end
Loading ActiveRecord Object / ActiveRecord Collection
class Event < ActiveRecord::Base
belongs_to :device
end
class Device < ActiveRecord::Base
has_many :events
end
class EventType < GraphQL::Schema::Object
field :device, DeviceType, null: false
def device
dataloader
.with(GraphQL::Sources::ActiveRecordObject, ::Device, key: :id)
.load(object.device_id)
end
end
class DeviceType < GraphQL::Schema::Object
field :events, [EventType], null: false
def events
dataloader
.with(GraphQL::Sources::ActiveRecordCollection, ::Event, key: :device_id)
.load(object)
end
end
Loading Counts
class Like
belongs_to :post
end
class Post
has_many :likes
end
class PostType < GraphQL::Schema::Object
field :likes, Integer, null: false
def likes
dataloader
.with(GraphQL::Sources::ActiveRecordCount, ::Like, key: :post_id)
.load(object.id)
end
end
SELECT "likes"."post_id", COUNT(*)
FROM "likes"
WHERE "likes"."post_id" IN (1, 2, 3, ...)
GROUP BY "likes"."post_id"
Loading Exists
class User
has_many :purchases
end
class Purchase
belongs_to :product
belongs_to :user
end
class Product
has_many :purchases
end
class ProductType
field :purchased, Boolean, null: false
def purchased
dataloader
.with(GraphQL::Sources::ActiveRecordExists, ::Purchase.where(user: context.user), key: :product_id)
.load(object.id)
end
end
Loading with Rails.cache
class UserType < GraphQL::Schema::Object
field :location, String, null: false
def location
dataloader
.with(GraphQL::Sources::RailsCache)
.load(key: "geocode:#{object.latest_ip}", fallback: -> { Geocode.for(object.latest_ip) })
end
end
License
The gem is available as open source under the terms of the MIT License.