{"id":13878158,"url":"https://github.com/ksylvest/graphql-sources","last_synced_at":"2025-04-09T20:03:56.027Z","repository":{"id":42037268,"uuid":"510493187","full_name":"ksylvest/graphql-sources","owner":"ksylvest","description":"A collection of common GraphQL sources for working with Ruby on Rails.","archived":false,"fork":false,"pushed_at":"2025-03-31T16:29:08.000Z","size":88,"stargazers_count":24,"open_issues_count":1,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-09T20:03:50.898Z","etag":null,"topics":["graphql","rails","ruby"],"latest_commit_sha":null,"homepage":"https://graphql-sources.ksylvest.com/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ksylvest.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-04T20:21:22.000Z","updated_at":"2025-02-15T18:42:40.000Z","dependencies_parsed_at":"2024-10-23T06:22:53.860Z","dependency_job_id":"dd008740-571a-406d-8d8b-7e79d5d0afd5","html_url":"https://github.com/ksylvest/graphql-sources","commit_stats":{"total_commits":120,"total_committers":4,"mean_commits":30.0,"dds":"0.44999999999999996","last_synced_commit":"b07f1d21d2b476531cb397b149ea7a30c288df6d"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ksylvest%2Fgraphql-sources","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ksylvest%2Fgraphql-sources/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ksylvest%2Fgraphql-sources/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ksylvest%2Fgraphql-sources/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ksylvest","download_url":"https://codeload.github.com/ksylvest/graphql-sources/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248103864,"owners_count":21048245,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graphql","rails","ruby"],"created_at":"2024-08-06T08:01:41.378Z","updated_at":"2025-04-09T20:03:55.998Z","avatar_url":"https://github.com/ksylvest.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# GraphQL::Sources\n\n[![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ksylvest/graphql-sources/blob/main/LICENSE)\n[![RubyGems](https://img.shields.io/gem/v/graphql-sources)](https://rubygems.org/gems/graphql-sources)\n[![GitHub](https://img.shields.io/badge/github-repo-blue.svg)](https://github.com/ksylvest/graphql-sources)\n[![Yard](https://img.shields.io/badge/docs-site-blue.svg)](https://graphql-sources.ksylvest.com)\n[![CircleCI](https://img.shields.io/circleci/build/github/ksylvest/graphql-sources)](https://circleci.com/gh/ksylvest/graphql-sources)\n[![Maintainability](https://api.codeclimate.com/v1/badges/bc301cb72712637e67dd/maintainability)](https://codeclimate.com/github/ksylvest/graphql-sources/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/bc301cb72712637e67dd/test_coverage)](https://codeclimate.com/github/ksylvest/graphql-sources/test_coverage)\n\n`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.\n\n## Installation\n\nInstall the gem and add to the application's Gemfile by executing:\n\n    $ bundle add graphql-sources\n\nIf bundler is not being used to manage dependencies, install the gem by executing:\n\n    $ gem install graphql-sources\n\nThe `GraphQL::Dataloader` plugin must be installed in the schema:\n\n```ruby\nclass AppSchema \u003c GraphQL::Schema\n  use GraphQL::Dataloader\n  # ...\nend\n```\n\n## Usage\n\n### Loading `belongs_to` / `has_many` Associations\n\n```ruby\nclass Purchase \u003c ActiveRecord::Base\n  belongs_to :customer\nend\n```\n\n```ruby\nclass Customer \u003c ActiveRecord::Base\n  has_many :purchases\nend\n```\n\n```ruby\nclass PurchaseType \u003c GraphQL::Schema::Object\n  field :customer, CustomerType, null: false\n\n  # @return [Customer]\n  def customer\n    # SELECT * FROM \"customers\" WHERE \"customers\".\"id\" IN (...)\n    dataloader\n      .with(GraphQL::Sources::ActiveRecordAssociation, :customer)\n      .load(object)\n  end\nend\n```\n\n```ruby\nclass CustomerType \u003c GraphQL::Schema::Object\n  field :purchases, [PurchaseType], null: false\n\n  def purchases\n    # SELECT * FROM \"customers\" WHERE \"customers\".\"id\" IN (...)\n    dataloader\n      .with(GraphQL::Sources::ActiveRecordAssociation, :purchases)\n      .load(object)\n  end\nend\n```\n\n### Loading `belongs_to` / `has_one` Associations\n\n```ruby\nclass Profile \u003c ActiveRecord::Base\n  belongs_to :user\nend\n```\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  has_one :profile\nend\n```\n\n```ruby\nclass ProfileType \u003c GraphQL::Schema::Object\n  field :user, [UserType], null: false\n\n  # @return [User]\n  def user\n    # SELECT * FROM \"users\" WHERE \"users\".\"id\" IN (...)\n    dataloader\n      .with(GraphQL::Sources::ActiveRecordAssociation, :user)\n      .load(object)\n  end\nend\n```\n\n```ruby\nclass UserType \u003c GraphQL::Schema::Object\n  field :profile, [ProfileType], null: false\n\n  # @return [Profile]\n  def profile\n      # SELECT * FROM \"profiles\" WHERE \"profiles\".\"id\" IN (...)\n    dataloader\n      .with(GraphQL::Sources::ActiveRecordAssociation, :profile)\n      .load(object)\n  end\nend\n```\n\n### Loading `has_and_belongs_to_many` Associations\n\n```ruby\nclass Student\n  has_and_belongs_to_many :courses\nend\n```\n\n```ruby\nclass Course\n  has_and_belongs_to_many :students\nend\n```\n\n```ruby\nclass StudentType \u003c GraphQL::Schema::Object\n  field :courses, [CourseType], null: false\n\n  # @return [Array\u003cCourse\u003e]\n  def courses\n    # SELECT * FROM \"courses_students\" WHERE \"courses_students\".\"student_id\" = IN (...)\n    # SELECT * FROM \"courses\" WHERE \"courses\".\"id\" IN (...)\n    dataloader\n      .with(GraphQL::Sources::ActiveRecordAssociation, :courses)\n      .load(object)\n  end\nend\n```\n\n```ruby\nclass CourseType \u003c GraphQL::Schema::Object\n  field :students, [StudentType], null: false\n\n  # @return [Array\u003cStudent\u003e]\n  def students\n    # SELECT * FROM \"courses_students\" WHERE \"courses_students\".\"course_id\" = IN (...)\n    # SELECT * FROM \"students\" WHERE \"students\".\"id\" IN (...)\n    dataloader\n      .with(GraphQL::Sources::ActiveRecordAssociation, :students)\n      .load(object)\n  end\nend\n```\n\n### Loading `has_one_attached` Associations\n\n```ruby\nclass User\n  has_one_attached :photo\nend\n```\n\n```ruby\nclass UserType \u003c GraphQL::Schema::Object\n  field :avatar, AttachedType, null: false\n\n  # @return [ActiveStorage::Attachment]\n  def avatar\n    # SELECT \"active_storage_attachments\".*\n    # FROM \"active_storage_attachments\"\n    # WHERE \"active_storage_attachments\".\"name\" = 'avatar'\n    #   AND \"active_storage_attachments\".\"record_type\" = 'User'\n    #   AND \"active_storage_attachments\".\"record_id\" IN (...)\n    dataloader\n      .with(GraphQL::Sources::ActiveStorageHasOneAttached, :avatar)\n      .load(object)\n  end\nend\n```\n\n### Loading `has_many_attached` Associations\n\n```ruby\nclass User\n  has_many_attached :photos\nend\n```\n\n```ruby\nclass UserType \u003c GraphQL::Schema::Object\n  field :photos, [AttachedType], null: false\n\n  # @return [Array\u003cActiveStorage::Attachment\u003e]\n  def photos\n    # SELECT \"active_storage_attachments\".*\n    # FROM \"active_storage_attachments\"\n    # WHERE \"active_storage_attachments\".\"name\" = 'photos'\n    #   AND \"active_storage_attachments\".\"record_type\" = 'User'\n    #   AND \"active_storage_attachments\".\"record_id\" IN (...)\n    dataloader\n      .with(GraphQL::Sources::ActiveStorageHasManyAttached, :photos)\n      .load(object)\n  end\nend\n```\n\n### Loading ActiveRecord Object / ActiveRecord Collection\n\n```ruby\nclass Event \u003c ActiveRecord::Base\n  belongs_to :device\nend\n```\n\n```ruby\nclass Device \u003c ActiveRecord::Base\n  has_many :events\nend\n```\n\n```ruby\nclass EventType \u003c GraphQL::Schema::Object\n  field :device, DeviceType, null: false\n\n  # @return [Device]\n  def device\n    # SELECT * FROM \"devices\" WHERE \"devices\".\"id\" IN (...)\n    dataloader\n      .with(GraphQL::Sources::ActiveRecordObject, ::Device, key: :id)\n      .load(object.device_id)\n  end\nend\n```\n\n```ruby\nclass DeviceType \u003c GraphQL::Schema::Object\n  field :events, [EventType], null: false\n\n  def events\n    # SELECT * FROM \"events\" WHERE \"events\".\"device_id\" IN (...)\n    dataloader\n      .with(GraphQL::Sources::ActiveRecordCollection, ::Event, key: :device_id)\n      .load(object)\n  end\nend\n```\n\n### Loading Counts\n\n```ruby\nclass Like\n  belongs_to :post\nend\n```\n\n```ruby\nclass Post\n  has_many :likes\nend\n```\n\n```ruby\nclass PostType \u003c GraphQL::Schema::Object\n  field :likes, Integer, null: false\n\n  def likes\n    dataloader\n      .with(GraphQL::Sources::ActiveRecordCount, ::Like, key: :post_id)\n      .load(object.id)\n  end\nend\n```\n\n```sql\nSELECT \"likes\".\"post_id\", COUNT(*)\nFROM \"likes\"\nWHERE \"likes\".\"post_id\" IN (1, 2, 3, ...)\nGROUP BY \"likes\".\"post_id\"\n```\n\n### Loading Exists\n\n```ruby\nclass User\n  has_many :purchases\nend\n```\n\n```ruby\nclass Purchase\n  belongs_to :product\n  belongs_to :user\nend\n```\n\n```ruby\nclass Product\n  has_many :purchases\nend\n```\n\n```ruby\nclass ProductType\n  field :purchased, Boolean, null: false\n\n  def purchased\n    dataloader\n      .with(GraphQL::Sources::ActiveRecordExists, ::Purchase.where(user: context.user), key: :product_id)\n      .load(object.id)\n  end\nend\n```\n\n### Loading with `Rails.cache`\n\n```ruby\nclass UserType \u003c GraphQL::Schema::Object\n  field :location, String, null: false\n\n  def location\n    dataloader\n      .with(GraphQL::Sources::RailsCache)\n      .load(key: \"geocode:#{object.latest_ip}\", fallback: -\u003e { Geocode.for(object.latest_ip) })\n  end\nend\n```\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fksylvest%2Fgraphql-sources","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fksylvest%2Fgraphql-sources","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fksylvest%2Fgraphql-sources/lists"}