Skip to content

ksylvest/graphql-sources

Folders and files

NameName
Last commit message
Last commit date
Oct 27, 2024
Oct 25, 2024
Oct 27, 2024
Oct 27, 2024
Aug 2, 2024
Jul 5, 2022
Aug 2, 2024
Oct 25, 2024
Jul 5, 2022
Oct 25, 2024
Oct 27, 2024
Jul 5, 2022
Oct 27, 2024
Jul 5, 2022
Aug 2, 2024

Repository files navigation

GraphQL::Sources

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

  # @return [Customer]
  def customer
    # SELECT * FROM "customers" WHERE "customers"."id" IN (...)
    dataloader
      .with(GraphQL::Sources::ActiveRecordAssociation, :customer)
      .load(object)
  end
end
class CustomerType < GraphQL::Schema::Object
  field :purchases, [PurchaseType], null: false

  def purchases
    # SELECT * FROM "customers" WHERE "customers"."id" IN (...)
    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

  # @return [User]
  def user
    # SELECT * FROM "users" WHERE "users"."id" IN (...)
    dataloader
      .with(GraphQL::Sources::ActiveRecordAssociation, :user)
      .load(object)
  end
end
class UserType < GraphQL::Schema::Object
  field :profile, [ProfileType], null: false

  # @return [Profile]
  def profile
      # SELECT * FROM "profiles" WHERE "profiles"."id" IN (...)
    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

  # @return [Array<Course>]
  def courses
    # SELECT * FROM "courses_students" WHERE "courses_students"."student_id" = IN (...)
    # SELECT * FROM "courses" WHERE "courses"."id" IN (...)
    dataloader
      .with(GraphQL::Sources::ActiveRecordAssociation, :courses)
      .load(object)
  end
end
class CourseType < GraphQL::Schema::Object
  field :students, [StudentType], null: false

  # @return [Array<Student>]
  def students
    # SELECT * FROM "courses_students" WHERE "courses_students"."course_id" = IN (...)
    # SELECT * FROM "students" WHERE "students"."id" IN (...)
    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

  # @return [ActiveStorage::Attachment]
  def avatar
    # SELECT "active_storage_attachments".*
    # FROM "active_storage_attachments"
    # WHERE "active_storage_attachments"."name" = 'avatar'
    #   AND "active_storage_attachments"."record_type" = 'User'
    #   AND "active_storage_attachments"."record_id" IN (...)
    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

  # @return [Array<ActiveStorage::Attachment>]
  def photos
    # SELECT "active_storage_attachments".*
    # FROM "active_storage_attachments"
    # WHERE "active_storage_attachments"."name" = 'photos'
    #   AND "active_storage_attachments"."record_type" = 'User'
    #   AND "active_storage_attachments"."record_id" IN (...)
    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

  # @return [Device]
  def device
    # SELECT * FROM "devices" WHERE "devices"."id" IN (...)
    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
    # SELECT * FROM "events" WHERE "events"."device_id" IN (...)
    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

Status

CircleCI Maintainability Test Coverage

License

The gem is available as open source under the terms of the MIT License.