{"id":13879792,"url":"https://github.com/keepworks/graphql-sugar","last_synced_at":"2025-11-11T11:39:37.409Z","repository":{"id":56875278,"uuid":"108324832","full_name":"keepworks/graphql-sugar","owner":"keepworks","description":"A sweet, extended DSL written on top of the graphql-ruby gem.","archived":false,"fork":false,"pushed_at":"2023-02-17T06:10:13.000Z","size":34,"stargazers_count":42,"open_issues_count":2,"forks_count":9,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-27T20:49:30.889Z","etag":null,"topics":["graphql","graphql-ruby","rails","ruby"],"latest_commit_sha":null,"homepage":null,"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/keepworks.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2017-10-25T20:52:42.000Z","updated_at":"2024-01-02T13:18:28.000Z","dependencies_parsed_at":"2024-11-24T08:31:59.937Z","dependency_job_id":"d10eca48-6743-4566-8d32-9a2e4c6cf9ee","html_url":"https://github.com/keepworks/graphql-sugar","commit_stats":{"total_commits":16,"total_committers":3,"mean_commits":5.333333333333333,"dds":0.1875,"last_synced_commit":"eb2a32ab96acb513180e00eb7666be3e656132d0"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/keepworks/graphql-sugar","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keepworks%2Fgraphql-sugar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keepworks%2Fgraphql-sugar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keepworks%2Fgraphql-sugar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keepworks%2Fgraphql-sugar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/keepworks","download_url":"https://codeload.github.com/keepworks/graphql-sugar/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keepworks%2Fgraphql-sugar/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272009606,"owners_count":24857823,"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","status":"online","status_checked_at":"2025-08-25T02:00:12.092Z","response_time":1107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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","graphql-ruby","rails","ruby"],"created_at":"2024-08-06T08:02:33.418Z","updated_at":"2025-10-08T17:12:34.707Z","avatar_url":"https://github.com/keepworks.png","language":"Ruby","readme":"# GraphQL::Sugar\n\nA sweet, extended DSL written on top of the [graphql-ruby](https://github.com/rmosolgo/graphql-ruby) gem.\n\n**Looking for a quick overview of this gem in action?** Head over to the [Usage](#usage) section.\n\nThis gem allows you to:\n\n* Easily write [object types](#object-types) and [input types](#input-types) that are backed by ActiveRecord models.\n  * Automatically convert field names to snake_case.\n  * Automatically add `id`, `createdAt` and `updatedAt` fields if these columns exist in your database schema.\n  * Automatically determine the type of the field, based on your database schema and model validation rules, keeping things DRY.\n* Easily write [resolvers](#resolvers) and [mutators](#mutators) to encapsulate query and mutation logic.\n  * Provide an object-oriented layer, allowing easy refactoring of common code across queries and mutations.\n  * Look like (and function very similar to) Rails controllers, so that writing them is a breeze.\n\n## Installation\n\n```ruby\ngem 'graphql'\ngem 'graphql-sugar'\n```\n\nAnd then execute:\n\n    $ bundle\n\nAnd finally, do some initial setup:\n\n    $ rails g graphql:sugar\n\n## Usage\n\nThis section provides a quick overview of the how simple the DSL can be, as well as a general workflow to follow:\n\n### Writing Queries\n\nCreate the ObjectType:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\n\n  attribute :title\n  attribute :content\n  attribute :isPublic\n\n  relationship :user\n  relationship :comments\nend\n```\n\nCreate a [Resolver](#resolvers):\n\n```ruby\nclass PostResolver \u003c ApplicationResolver\n  parameter :id, !types.ID\n\n  def resolve\n    Post.find(params[:id])\n  end\nend\n```\n\nExpose the Resolver:\n\n```ruby\nTypes::QueryType = GraphQL::ObjectType.define do\n  name 'Query'\n\n  resolver :post\nend\n```\n\n### Writing Mutations\n\nCreate the InputObjectType:\n\n```ruby\nInputs::PostInputType = GraphQL::InputObjectType.define do\n  name 'PostInput'\n\n  model_class Post\n\n  parameter :title\n  parameter :content\nend\n```\n\nCreate a [Mutator](#mutators):\n\n```ruby\nclass CreatePostMutator \u003c ApplicationMutator\n  parameter :input, !Inputs::PostInputType\n\n  type !Types::PostType\n\n  def mutate\n    Post.create!(params[:input])\n  end\nend\n```\n\nExpose the Mutator:\n\n```ruby\nTypes::MutationType = GraphQL::ObjectType.define do\n  name 'Mutation'\n\n  mutator :createPost\nend\n```\n\n## Usage\n\n### Object Types\n\nStart by generating an ObjectType as you normally would:\n\n    $ rails g graphql:object Post\n\nThis would create the following under `app/graphql/types/post_type.rb`:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  name \"Post\"\nend\n```\n\nReplace the `name` line with a `model_class` declaration:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\nend\n```\n\nThis automatically sets the name as `PostType`. If you wish to overwrite the name, you can pass a second argument:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post, 'PostObject'\nend\n```\n\nThe `model_class` declaration is **required** to use rest of the extended ObjectType DSL (like `attributes`, `attribute`, `relationships`, `relationship`, etc). If you forget to declare it however, a helpful exception is raised. :smile:\n\n#### Defining attributes\n\n*Normally*, this is how you would add a couple of fields to your ObjectType:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\n\n  field :id, !types.ID\n  field :title, !types.String\n  field :content, types.String\n  field :isPublic, !types.Boolean, property: :is_public\n  field :createdAt\n  field :updatedAt\nend\n```\n\nHowever, using GraphQL::Sugar, you can now shorten this to:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\n\n  attribute :title\n  attribute :content\n  attribute :isPublic\nend\n```\n\nUnder the hood:\n\n* The `id`, `createdAt` and `updatedAt` fields are automatically added if your model has those attributes.\n* The type for the rest of the fields are automatically determined based on your `schema.rb` and model validations. (Read more about [automatic type resolution](#automatic-type-resolution).)\n* The fields automatically resolve to the snake_cased method names of the attribute name provided (eg. `isPublic` =\u003e `is_public`).\n\nYou can shorten this further [active_model_serializers](https://github.com/rails-api/active_model_serializers)-style:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\n\n  attributes :title, :content, :isPublic\nend\n```\n\nOr even more simply:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\n\n  attributes\nend\n```\n\n... which automatically includes *all* the attributes of a model based on your schema. While NOT recommended for production, this provides easy scaffolding of model-backed object types during development.\n\nInternally `attribute` just defines a `field`, but automatically determines the type and resolves to the model's snake_cased attribute. For simplicity, it follows the *exact same syntax* as `field`, so you can override type or specify a `resolve:` function:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\n\n  attribute :thumbnail, types.String, resolve: -\u003e(obj, args, ctx) { obj.picture_url(:thumb) }\nend\n```\n\nThis is useful (and necessary) if you wish to expose `attr_accessor`s defined in your model. (Read more about [automatic type resolution](#automatic-type-resolution).)\n\n**Side Note:** You _can_ always mix in good ol' `field`s along with `attribute`s if you really need to access the old DSL:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\n\n  attribute :title\n  field :isArchived, types.Boolean, resolve: -\u003e(obj, args, ctx) { obj.is_archived? }\nend\n```\n\nHowever, since the syntax is pretty much the same, it is preferable to use either `field` or `attribute` throughout the type definition for the sake of uniformity. You may have a non-model backed ObjectType for example, which can use `field`s.\n\n#### Defining relationships\n\nAssume the Post model has the following associations:\n\n```ruby\nclass Post \u003c ApplicationRecord\n  belongs_to :user\n  has_many :comments\nend\n```\n\n*Normally*, this is how you would define the relationship in your ObjectType:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\n\n  field :userId, !types.ID, property: :user_id\n  field :user, Types::UserType\n\n  field :comments, !types[Types::CommentType]\nend\n```\n\nHowever, using GraphQL::Sugar, you can now shorten this to:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\n\n  relationship :user\n  relationship :comments\nend\n```\n\nUnder the hood:\n\n* If the relationship is **belongs_to**, it automatically defines a field for the corresponding foreign key. It also determines the type and marks the association as non-null using [automatic type resolution](#automatic-type-resolution).\n* If the relationship is **has_one** or **has_many**, it first looks for a corresponding [Resolver](#resolvers) (eg. in this case, `CommentsResolver`). If it doesn't find one, it defaults to calling method of the underlying association on the object (eg. `obj.comments`)\n\nYou can shorten the above code to:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\n\n  relationships :user, :comments\nend\n```\n\nOr even more simply:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\n\n  relationships\nend\n```\n\n... which automatically reflects on *all* your model associations and includes them. While NOT recommended for production, this provides easy scaffolding of model-backed object types during development.\n\n**Side Note:** Unlike `attribute`, `relationship` is not just syntactic sugar for `field` and it does much more. It is recommended that you revert to using `field`s (rather than `attribute`) if you need to achieve a specific behavior involving associations. For example:\n\n```ruby\nTypes::PostType = GraphQL::ObjectType.define do\n  model_class Post\n\n  relationship :user\n\n  field :recentComments, !types[Types::CommentType], resolve: -\u003e(obj, args, ctx) {\n    obj.comments.not_flagged.recent.limit(3)\n  }\n  end\nend\n```\n\n#### Automatic Type Resolution\n\nYour model attribute's type is automatically determined using Rails' reflection methods, as follows:\n\n* First, we look at the column type:\n  * `:integer` gets mapped to `types.Int` (`GraphQL::INT_TYPE`),\n  * `:float` and `:decimal` get mapped to `types.Float` (`GraphQL::FLOAT_TYPE`),\n  * `:boolean` gets mapped to `types.Boolean` (`GraphQL::BOOLEAN_TYPE`),\n  * and the rest get mapped to `types.String` (`GraphQL::STRING_TYPE`).\n* Then, we determine the non-nullability based on whether:\n  * You have specified `null: false` for the column in your schema, or\n  * You have specified `presence: true` validation for the attribute in your model.\n\nIn instances where a type cannot be automatically determined, you must provide the type yourself. For example, `attr_accessor`s are not persisted and don't have a corresponding column in your database schema.\n\n### Input Types\n\n*Normally*, this is how you would define your InputObjectType:\n\n```ruby\nInputs::PostInputType = GraphQL::InputObjectType.define do\n  name 'PostInput'\n\n  argument :title, types.String\n  argument :content, types.String\n  argument :isPublic, types.Boolean, as: :is_public\nend\n```\n\nHowever, using GraphQL::Sugar, you can now shorten this to:\n\n```ruby\nInputs::PostInputType = GraphQL::InputObjectType.define do\n  name 'PostInput'\n\n  model_class 'Post'\n\n  parameter :title\n  parameter :content\n  parameter :isPublic\nend\n```\n\nUnder the hood,\n* `parameter` uses the same [automatic type resolution](#automatic-type-resolution) as `attribute`, but creates arguments that are not-null by default. The default behavior passes all values to be validated in the model instead, in order to return proper error messages in the response. (**TODO:** Allow this behavior to be configured via an initializer.)\n* It allows sets the `:as` value to the snake_cased form of the provided name. (eg. `:isPublic` =\u003e `:is_public`). This allows us to easily pass them into ActiveRecord's `create` and `update_attributes` methods.\n\nYou can override the type to make a field non-null as follows:\n\n```ruby\nInputs::PostInputType = GraphQL::InputObjectType.define do\n  name 'PostInput'\n\n  model_class 'Post'\n\n  parameter :title, !types.String\n  parameter :content\nend\n```\n\n### Resolvers\n\nIn its simplest form, a Resolver simply inherits from `ApplicationResolver` and contains a `#resolve` method.\n\n```ruby\nclass PostsResolver \u003c ApplicationResolver\n  def resolve\n    Post.all\n  end\nend\n```\n\nTo expose the resolver as a field, declare it in your root QueryType:\n\n```ruby\nTypes::QueryType = GraphQL::ObjectType.define do\n  name 'Query'\n\n  resolver :posts\nend\n```\n\nTo declare arguments, you can use the `parameter` keyword which follows the same syntax:\n\n```ruby\nclass PostResolver \u003c ApplicationResolver\n  parameter :id, !types.ID\n\n  def resolve\n    Post.find(params[:id])\n  end\nend\n```\n\nThe benefit is that all `parameter`s (read: arguments) are loaded into a `params` object, with all keys transformed into snake_case. This allows them to be easily used with ActiveRecord methods like `where` and `find_by`.\n\nYou also have `object` and `context` available in your resolve method:\n\n```ruby\nclass PostsResolver \u003c ApplicationResolver\n  def resolve\n    (object || context[:current_user]).posts\n  end\nend\n```\n\n#### Thinking in Graphs *using Resolvers*\n\nAssume the following GraphQL query (\"fetch 10 posts, along with the authors and 2 of their highest rated posts.\"):\n\n```\nquery {\n  posts(limit: 10) {\n    title\n    content\n\n    user {\n      name\n\n      posts(limit: 2, sort: \"rating_desc\") {\n        title\n        rating\n      }\n    }\n  }\n}\n```\n\nWhen executed, we resolve both the first and second `posts` using `PostsResolver`. This means:\n\n1. All the `argument`s (or `parameter`s) available to your top level `posts` are available to all your nested `posts`s through relationships without any extra work.\n\n2. The `object` value passed to your `PostsResolver#resolve` function is *very* important. This would be a good place to perform an authorization check to see if the current user has access to this relationship on the `object`.\n\n**A quick detour:** At the top of your graph, you have your **root_value** ([read more](http://graphql-ruby.org/queries/executing_queries.html#root-value)), which the [graphql-ruby](https://github.com/rmosolgo/graphql-ruby) library allows you to set for your schema. By default, this is `null`. You can either *explicitly* set this root_value, or *implicitly* consider to be the current user (or current organization, or whatever your application deems it to be).\n\nFor example,\n\n```ruby\nclass PostsResolver \u003c ApplicationResolver\n  def resolve\n    parent_object = (object || context[:current_user])\n    authorize! :view_posts, parent_object\n\n    parent_object.posts\n  end\nend\n```\n\n### Mutators\n\nIn its simplest form, a Mutator simply inherits from `ApplicationMutator` and contains a `#mutate` method:\n\n```ruby\nclass CreatePostMutator \u003c ApplicationMutator\n  parameter :input, !Inputs::PostInputType\n\n  type !Types::PostType\n\n  def mutate\n    Post.create!(params[:input])\n  end\nend\n```\n\nTo expose the mutator as a field, declare it in your root MutationType:\n\n```ruby\nTypes::MutationType = GraphQL::ObjectType.define do\n  name 'Mutation'\n\n  mutator :createPost\nend\n```\n\nJust like resolvers, you have access to `object`, `params` and `context`:\n\n```ruby\nclass UpdatePostMutator \u003c ApplicationMutator\n  parameter :id, !types.ID\n  parameter :input, !Inputs::PostInputType\n\n  type !Types::PostType\n\n  def mutate\n    post = context[:current_user].posts.find(params[:id])\n    post.update_attributes!(params[:input])\n    post\n  end\nend\n```\n\n### Organizing Your Code\n\nWhen you install the gem using `rails g graphql:sugar`, it creates the following files:\n\n```\napp/graphql/functions/application_function.rb\napp/graphql/resolvers/application_resolver.rb\napp/graphql/mutators/application_mutator.rb\n```\n\nAll your resolvers inherit from `ApplicationResolver` and all your mutators inherit from `ApplicationMutator`, both of which in turn inherit from `ApplicationFunction`. You can use these classes to write shared code common to multiple queries, mutations, or both.\n\n#### Applying OO principles\n\n*Pagination and Sorting:* You can easily create methods that enable common features.\n\n```ruby\nclass ApplicationResolver \u003c ApplicationFunction\n  include GraphQL::Sugar::Resolver\n\n  def self.sortable\n    parameter :sort, types.String\n    parameter :sortDir, types.String\n  end\nend\n```\n\nUse in your other resolvers:\n\n```ruby\nclass PostsResolver \u003c ApplicationResolver\n  sortable\n\n  def resolve\n    # ...\n  end\nend\n```\n\n*Shared Code:* You can also easily share common code across a specific set of mutators. For example, your `CreatePostMutator` and `UpdatePostMutator` could inherit from `PostMutator`, which inherits from `ApplicationMutator`.\n\n#### Tips for Large Applications\n\nIn a large app, you can quite easily end up with tons of mutations. During setup, GraphQL::Sugar adds a few lines to your eager_load_paths so you can group them in folders, while maintaining mutations at the root level. For example,\n\n```\n# Folder Structure\napp/graphql/mutators/\n- posts\n  - create_post_mutator.rb\n  - update_post_mutator.rb\n- users\n  - create_user_mutator.rb\n  - update_user_mutator.rb\n- application_mutator.rb\n```\n\n```ruby\nTypes::MutationType = GraphQL::ObjectType.define do\n  name 'Mutation'\n\n  mutator :createPost\n  mutator :updatePost\n\n  mutator :createUser\n  mutator :updateUser\nend\n```\n\n### Generators\n\nA few basic generators have been written to quickly create some of the boilerplate code. They may not work perfectly, and the generated code may require further editing.\n\n    $ rails g graphql:resolver BlogPosts\n\nCreates a `BlogPostsResolver` class at `app/graphql/resolvers/blog_posts_resolver.rb`.\n\n    $ rails g graphql:mutator CreateBlogPost\n\nCreates a `CreateBlogPostMutator` class under `app/graphql/mutators/create_blog_post_mutator.rb`.\n\n## Credits\n\nMany thanks to the work done by the authors of the following gems, which this gem uses as a foundation and/or inspiration:\n\n- [graphql-ruby](https://github.com/rmosolgo/graphql-ruby)\n- [graphql-activerecord](https://github.com/goco-inc/graphql-activerecord)\n- [graphql-rails-resolver](https://github.com/colepatrickturner/graphql-rails-resolver)\n- [active_model_serializers](https://github.com/rails-api/active_model_serializers)\n\n---\n\nMaintained and sponsored by [KeepWorks](http://www.keepworks.com).\n\n![KeepWorks](http://www.keepworks.com/assets/logo-800bbf55fabb3427537cf669dc8cd018.png \"KeepWorks\")\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/keepworks/graphql-sugar. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeepworks%2Fgraphql-sugar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkeepworks%2Fgraphql-sugar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeepworks%2Fgraphql-sugar/lists"}