https://github.com/ksylvest/graphql-sources
A collection of common GraphQL sources for working with Ruby on Rails.
https://github.com/ksylvest/graphql-sources
graphql rails ruby
Last synced: about 1 year ago
JSON representation
A collection of common GraphQL sources for working with Ruby on Rails.
- Host: GitHub
- URL: https://github.com/ksylvest/graphql-sources
- Owner: ksylvest
- License: mit
- Created: 2022-07-04T20:21:22.000Z (almost 4 years ago)
- Default Branch: main
- Last Pushed: 2025-03-31T16:29:08.000Z (about 1 year ago)
- Last Synced: 2025-04-09T20:03:50.898Z (about 1 year ago)
- Topics: graphql, rails, ruby
- Language: Ruby
- Homepage: https://graphql-sources.ksylvest.com/
- Size: 85.9 KB
- Stars: 24
- Watchers: 2
- Forks: 3
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# GraphQL::Sources
[](https://github.com/ksylvest/graphql-sources/blob/main/LICENSE)
[](https://rubygems.org/gems/graphql-sources)
[](https://github.com/ksylvest/graphql-sources)
[](https://graphql-sources.ksylvest.com)
[](https://circleci.com/gh/ksylvest/graphql-sources)
[](https://codeclimate.com/github/ksylvest/graphql-sources/maintainability)
[](https://codeclimate.com/github/ksylvest/graphql-sources/test_coverage)
`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.
## 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:
```ruby
class AppSchema < GraphQL::Schema
use GraphQL::Dataloader
# ...
end
```
## Usage
### Loading `belongs_to` / `has_many` Associations
```ruby
class Purchase < ActiveRecord::Base
belongs_to :customer
end
```
```ruby
class Customer < ActiveRecord::Base
has_many :purchases
end
```
```ruby
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
```
```ruby
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
```ruby
class Profile < ActiveRecord::Base
belongs_to :user
end
```
```ruby
class User < ActiveRecord::Base
has_one :profile
end
```
```ruby
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
```
```ruby
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
```ruby
class Student
has_and_belongs_to_many :courses
end
```
```ruby
class Course
has_and_belongs_to_many :students
end
```
```ruby
class StudentType < GraphQL::Schema::Object
field :courses, [CourseType], null: false
# @return [Array]
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
```
```ruby
class CourseType < GraphQL::Schema::Object
field :students, [StudentType], null: false
# @return [Array]
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
```ruby
class User
has_one_attached :photo
end
```
```ruby
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
```ruby
class User
has_many_attached :photos
end
```
```ruby
class UserType < GraphQL::Schema::Object
field :photos, [AttachedType], null: false
# @return [Array]
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
```ruby
class Event < ActiveRecord::Base
belongs_to :device
end
```
```ruby
class Device < ActiveRecord::Base
has_many :events
end
```
```ruby
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
```
```ruby
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
```ruby
class Like
belongs_to :post
end
```
```ruby
class Post
has_many :likes
end
```
```ruby
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
```
```sql
SELECT "likes"."post_id", COUNT(*)
FROM "likes"
WHERE "likes"."post_id" IN (1, 2, 3, ...)
GROUP BY "likes"."post_id"
```
### Loading Exists
```ruby
class User
has_many :purchases
end
```
```ruby
class Purchase
belongs_to :product
belongs_to :user
end
```
```ruby
class Product
has_many :purchases
end
```
```ruby
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`
```ruby
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](https://opensource.org/licenses/MIT).