
Security News
Follow-up and Clarification on Recent Malicious Ruby Gems Campaign
A clarification on our recent research investigating 60 malicious Ruby gems.
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.
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
belongs_to
/ has_many
Associationsclass 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
belongs_to
/ has_one
Associationsclass 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
has_and_belongs_to_many
Associationsclass 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
has_one_attached
Associationsclass 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
has_many_attached
Associationsclass 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
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
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"
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
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
The gem is available as open source under the terms of the MIT License.
FAQs
Unknown package
We found that graphql-sources demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A clarification on our recent research investigating 60 malicious Ruby gems.
Security News
ESLint now supports parallel linting with a new --concurrency flag, delivering major speed gains and closing a 10-year-old feature request.
Research
/Security News
A malicious Go module posing as an SSH brute forcer exfiltrates stolen credentials to a Telegram bot controlled by a Russian-speaking threat actor.