{"id":13879271,"url":"https://github.com/hschne/graphql-groups","last_synced_at":"2025-04-07T12:11:09.913Z","repository":{"id":48687426,"uuid":"270384800","full_name":"hschne/graphql-groups","owner":"hschne","description":"Run group- and aggregation queries with graphql-ruby","archived":false,"fork":false,"pushed_at":"2025-03-12T19:30:20.000Z","size":259,"stargazers_count":26,"open_issues_count":1,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-31T11:05:20.818Z","etag":null,"topics":["graphql","graphql-groups","graphql-ruby","ruby","ruby-gem","ruby-library"],"latest_commit_sha":null,"homepage":"","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/hschne.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":"2020-06-07T17:46:52.000Z","updated_at":"2025-01-01T11:17:59.000Z","dependencies_parsed_at":"2024-10-02T19:00:29.670Z","dependency_job_id":"aa776df0-b137-4cc1-97d1-c4ea3d20353e","html_url":"https://github.com/hschne/graphql-groups","commit_stats":{"total_commits":88,"total_committers":3,"mean_commits":"29.333333333333332","dds":0.06818181818181823,"last_synced_commit":"6d350ff4039fcc52a6e3dfe58e4a953db683452d"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hschne%2Fgraphql-groups","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hschne%2Fgraphql-groups/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hschne%2Fgraphql-groups/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hschne%2Fgraphql-groups/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hschne","download_url":"https://codeload.github.com/hschne/graphql-groups/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247648977,"owners_count":20972945,"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","graphql-groups","graphql-ruby","ruby","ruby-gem","ruby-library"],"created_at":"2024-08-06T08:02:15.800Z","updated_at":"2025-04-07T12:11:09.876Z","avatar_url":"https://github.com/hschne.png","language":"Ruby","readme":"# GraphQL Groups\n\n[![Gem Version](https://badge.fury.io/rb/graphql-groups.svg)](https://badge.fury.io/rb/graphql-groups)\n[![Build Status](https://github.com/hschne/graphql-groups/workflows/Build/badge.svg)](https://github.com/hschne/graphql-groups/workflows/Build/badge.svg)\n[![Maintainability](https://api.codeclimate.com/v1/badges/692d4125ac8548fb145e/maintainability)](https://codeclimate.com/github/hschne/graphql-groups/maintainability) \n[![Test Coverage](https://api.codeclimate.com/v1/badges/692d4125ac8548fb145e/test_coverage)](https://codeclimate.com/github/hschne/graphql-groups/test_coverage)\n\nRun group- and aggregation queries with [graphql-ruby](https://github.com/rmosolgo/graphql-ruby).\n\n## Installation\n\nAdd this line to your application's Gemfile and run `bundle install`.\n\n```ruby\ngem 'graphql-groups'\n```\n```bash\n$ bundle install\n```\n\n## Usage\n\nSuppose you want to get the number of authors, grouped by their age. Create a new group type by inheriting from `GraphQL::Groups::GroupType`:\n\n```ruby\nclass AuthorGroupType \u003c GraphQL::Groups::Schema::GroupType\n  scope { Author.all }\n\n  by :age\nend\n```\n\nInclude the new type in your schema using the `group` keyword, and you are done.\n\n```ruby\nclass QueryType \u003c GraphQL::Schema::Object\n  include GraphQL::Groups\n\n  group :author_group_by, AuthorGroupType\nend\n```\n\nYou can then run the following query to retrieve the number of authors per age. \n```graphql\nquery myQuery{ \n  authorGroupBy {\n    age {\n      key\n      count\n    }\n }\n}\n```\n```json\n{\n  \"authorGroupBy\":{\n    \"age\":[\n      {\n        \"key\":\"31\",\n        \"count\":1\n      },\n      {\n        \"key\":\"35\",\n        \"count\":3\n      },\n      ...\n    ]\n  }\n}\n```\n\n\n## Why? \n\n`graphql-ruby` lacks a built in way to retrieve statistical data, such as counts or averages. It is possible to implement custom queries that provide  this functionality by using `group_by` (see for example [here](https://dev.to/gopeter/how-to-add-a-groupby-field-to-your-graphql-api-1f2j)), but this performs poorly for large amounts of data. \n\n`graphql-groups` allows you to write flexible, readable queries while leveraging your database to aggreate data. It does so by performing an AST analysis on your request and executing exactly the database queries needed to fulfill it. This performs much better than grouping and aggregating in memory. See [performance](#Performance) for a benchmark.\n\n\n## Advanced Usage\n\nFor a showcase of what you can do with `graphql-groups` check out [graphql-groups-demo](https://github.com/hschne/graphql-groups-demo) \n\nFind a hosted version of the demo app [on Heroku](https://graphql-groups-demo.herokuapp.com/). \n\n### Grouping by Multiple Attributes\n\nThis library really shines when you want to group by multiple attributes, or otherwise retrieve complex statistical information\nwithin a single GraphQL query. \n\nFor example, to get the number of authors grouped by their name, and then also by age, you could construct a query similar to this: \n\n```graphql\nquery myQuery{ \n  authorGroups {\n    name {\n      key\n      count\n      groupBy {\n       age {\n        key\n        count\n       }\n      }\n    }\n }\n}\n``` \n\n```json\n{\n  \"authorGroups\":{\n    \"name\":[\n      {\n        \"key\":\"Ada\",\n        \"count\":2,\n        \"groupBy\":  {\n          \"age\": [\n            {\n            \"key\":\"30\",\n            \"count\":1\n            },\n            {\n            \"key\":\"35\",\n            \"count\":1\n            }\n          ]     \n        }\n      },\n      ...\n    ]\n  }\n}\n```\n\n`graphql-groups` will automatically execute the required queries and return the results in a easily parsable response.\n\n### Custom Grouping Queries\n\nTo customize which queries are executed to group items, you may specify the grouping query by creating a method of the same name in the group type.\n\n```ruby\nclass AuthorGroupType \u003c GraphQL::Groups::Schema::GroupType\n  scope { Author.all }\n\n  by :age\n\n  def age(scope:)\n    scope.group(\"(cast(age/10 as int) * 10) || '-' || ((cast(age/10 as int) + 1) * 10)\")\n  end\nend\n```\n\nYou may also pass arguments to custom grouping queries. In this case, pass any arguments to your group query as keyword arguments. \n\n```ruby\nclass BookGroupType \u003c GraphQL::Groups::Schema::GroupType\n  scope { Book.all }\n\n  by :published_at do\n    argument :interval, String, required: false\n  end\n\n  def published_at(scope:, interval: nil)\n    case interval\n    when 'month'\n      scope.group(\"strftime('%Y-%m-01 00:00:00 UTC', published_at)\")\n    when 'year'\n      scope.group(\"strftime('%Y-01-01 00:00:00 UTC', published_at)\")\n    else\n      scope.group(\"strftime('%Y-%m-%d 00:00:00 UTC', published_at)\")\n    end\n  end\nend\n```\n\nYou may access the query `context` in custom queries. As opposed to resolver methods accessing `object` is not possible and will raise an error. \n\n```ruby\nclass BookGroupType \u003c GraphQL::Groups::Schema::GroupType\n  scope { Book.all }\n\n  by :list_price\n\n  def list_price(scope:)\n    currency = context[:currency] || ' $'\n    scope.group(\"list_price || ' #{currency}'\")\n  end\nend\n```\n\n### Custom Scopes\n\nWhen defining a group type's scope you may access the parents `object` and `context`. \n\n```ruby\nclass QueryType \u003c GraphQL::Schema::Object\n  field :statistics, StatisticsType, null: false\n\n  def statistics\n    Book.all\n  end\nend\n\nclass StatisticsType \u003c GraphQL::Schema::Object\n  include GraphQL::Groups\n\n  group :books, BookGroupType\nend\n\nclass BookGroupType \u003c GraphQL::Groups::Schema::GroupType\n  # `object` refers to `Book.all`\n  scope { object.where(author_id: context[:current_person]) }\n\n  by :name\nend\n```\n\n### Custom Aggregates\n\nPer default `graphql-groups` supports aggregating `count` out of the box. If you need to other aggregates, such as sum or average \nyou may add them to your schema by creating a custom `GroupResultType`. Wire this up to your schema by specifying the result type in your\ngroup type.\n\n```ruby\nclass AuthorGroupResultType \u003c GraphQL::Groups::Schema::GroupResultType\n  aggregate :average do\n    attribute :age\n  end\nend\n```\n\n```ruby\nclass AuthorGroupType \u003c GraphQL::Groups::Schema::GroupType\n  scope { Author.all }\n\n  result_type { AuthorGroupResultType }\n\n  by :name\nend\n```\n\nPer default, the aggregate name and attribute will be used to construct the underlying aggregation query. The example above creates\n```ruby\nscope.average(:age)\n```\n\nIf you need more control over how to aggregate you may define a custom query by creating a method matching the aggregate name. The method *must* take the keyword arguments `scope` and `attribute`. \n\n```ruby\nclass AuthorGroupResultType \u003c GraphQL::Groups::Schema::GroupResultType\n  aggregate :average do\n    attribute :age\n  end\n\n  def average(scope:, attribute:)\n    scope.average(attribute)\n  end\nend\n```\n\nFor more examples see the [feature spec](./spec/graphql/feature_spec.rb) and [test schema](./spec/graphql/support/test_schema)\n\n## Performance\n\nWhile it is possible to add grouping to your GraphQL schema by using `group_by` (see [above](#why)) this performs poorly for large amounts of data. The graph below shows the number of requests per second possible with both implementations.\n\n![benchmark](benchmark/benchmark.jpg)\n\nThe benchmark queries the author count grouped by name, using an increasing number of authors. While the in-memory approach of grouping works well for a small number of records, it is outperformed quickly as that number increases.\n\nBenchmarks can be generated by running `rake benchmark`. The benchmark script used to generate the report be found [here](./benchmark/benchmark.rb)\n\n## Limitations and Known Issues\n\nPlease refer to the [issue tracker](https://github.com/hschne/graphql-groups/issues) for a list of known issues.\n\n## Credits\n\n\u003ca href=\"https://www.meisterlabs.com\"\u003e\u003cimg src=\"Meister.png\" width=\"50%\"\u003e\u003c/a\u003e\n\n[graphql-groups](https://github.com/hschne/graphql-groups) was created at [meister](https://www.meisterlabs.com/)\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/hschne/graphql-groups. 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](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Graphql::Groups project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/graphql-groups/blob/master/CODE_OF_CONDUCT.md).\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhschne%2Fgraphql-groups","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhschne%2Fgraphql-groups","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhschne%2Fgraphql-groups/lists"}