├── .gitmodules ├── docs ├── CNAME ├── .gitignore ├── favicon.ico ├── _includes │ ├── hierarchy-sub.md │ ├── table-of-contents.html │ ├── installation.md │ ├── search.html │ ├── tracking.html │ ├── type-creators.md │ ├── banner.html │ ├── type-description.md │ ├── 3-steps.html │ ├── head.html │ ├── breadcrumb.html │ ├── introspection-query.html │ └── features.md ├── _sass │ ├── support │ │ ├── mixins │ │ │ ├── mixins.scss │ │ │ ├── _buttons.scss │ │ │ ├── _layout.scss │ │ │ └── _typography.scss │ │ ├── support.scss │ │ └── _functions.scss │ ├── utilities │ │ ├── utilities.scss │ │ ├── _lists.scss │ │ ├── _typography.scss │ │ └── _layout.scss │ ├── mermaid.scss │ ├── modules.scss │ ├── labels.scss │ ├── mixins.scss │ ├── print.scss │ ├── typography.scss │ ├── overrides.scss │ ├── tables.scss │ ├── base.scss │ ├── callouts.scss │ └── code │ │ └── vs.scss ├── assets │ ├── images │ │ ├── logo.png │ │ ├── banner.png │ │ ├── github.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── release-banner.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ └── site.webmanifest │ ├── fonts │ │ ├── ITCAvantGardeStd-Bk.woff │ │ ├── ITCAvantGardeStd-Md.woff │ │ ├── ITCAvantGardeStd-Bk.woff2 │ │ ├── ITCAvantGardeStd-BkCn.woff │ │ ├── ITCAvantGardeStd-BkCn.woff2 │ │ ├── ITCAvantGardeStd-BkObl.woff │ │ ├── ITCAvantGardeStd-Bold.woff │ │ ├── ITCAvantGardeStd-Bold.woff2 │ │ ├── ITCAvantGardeStd-Demi.woff │ │ ├── ITCAvantGardeStd-Demi.woff2 │ │ ├── ITCAvantGardeStd-Md.woff2 │ │ ├── ITCAvantGardeStd-MdCn.woff │ │ ├── ITCAvantGardeStd-MdCn.woff2 │ │ ├── ITCAvantGardeStd-MdObl.woff │ │ ├── ITCAvantGardeStd-XLt.woff │ │ ├── ITCAvantGardeStd-XLt.woff2 │ │ ├── ITCAvantGardeStd-XLtCn.woff │ │ ├── ITCAvantGardeStd-BkCnObl.woff │ │ ├── ITCAvantGardeStd-BkObl.woff2 │ │ ├── ITCAvantGardeStd-BoldCn.woff │ │ ├── ITCAvantGardeStd-BoldCn.woff2 │ │ ├── ITCAvantGardeStd-BoldObl.woff │ │ ├── ITCAvantGardeStd-DemiCn.woff │ │ ├── ITCAvantGardeStd-DemiCn.woff2 │ │ ├── ITCAvantGardeStd-DemiObl.woff │ │ ├── ITCAvantGardeStd-MdCnObl.woff │ │ ├── ITCAvantGardeStd-MdObl.woff2 │ │ ├── ITCAvantGardeStd-XLtCn.woff2 │ │ ├── ITCAvantGardeStd-XLtObl.woff │ │ ├── ITCAvantGardeStd-XLtObl.woff2 │ │ ├── ITCAvantGardeStd-BkCnObl.woff2 │ │ ├── ITCAvantGardeStd-BoldCnObl.woff │ │ ├── ITCAvantGardeStd-BoldCnObl.woff2 │ │ ├── ITCAvantGardeStd-BoldObl.woff2 │ │ ├── ITCAvantGardeStd-DemiCnObl.woff │ │ ├── ITCAvantGardeStd-DemiCnObl.woff2 │ │ ├── ITCAvantGardeStd-DemiObl.woff2 │ │ ├── ITCAvantGardeStd-MdCnObl.woff2 │ │ ├── ITCAvantGardeStd-XLtCnObl.woff │ │ └── ITCAvantGardeStd-XLtCnObl.woff2 │ ├── js │ │ ├── site.js │ │ ├── events.js │ │ └── code.js │ └── css │ │ └── site.scss ├── robots.txt ├── _layouts │ ├── table-wrappers.html │ └── default.html ├── index.md ├── handbook │ └── snippets.md ├── 404.html ├── guides │ ├── advanced │ │ ├── abstract.md │ │ └── type-assignment.md │ ├── subscriptions │ │ ├── memory-store.md │ │ └── action-cable-provider.md │ ├── testing.md │ ├── recommendations.md │ ├── objects.md │ ├── sources │ │ └── scoped-arguments.md │ └── global-id.md └── Gemfile ├── test ├── assets │ ├── introspection-db.json │ ├── luke.jpg │ ├── en.yml │ └── introspection.gql ├── graphql │ ├── type │ │ ├── scalar │ │ │ ├── string_scalar_test.rb │ │ │ ├── binary_scalar_test.rb │ │ │ ├── float_scalar_test.rb │ │ │ ├── json_scalar_test.rb │ │ │ ├── id_scalar_test.rb │ │ │ ├── decimal_scalar_test.rb │ │ │ ├── int_scalar_test.rb │ │ │ ├── date_scalar_test.rb │ │ │ ├── date_time_scalar_test.rb │ │ │ ├── any_scalar_test.rb │ │ │ ├── time_scalar_test.rb │ │ │ ├── boolean_scalar_test.rb │ │ │ └── bigint_scalar_test.rb │ │ └── scalar_test.rb │ ├── request │ │ ├── component │ │ │ └── operation_test.rb │ │ └── context_test.rb │ └── type_test.rb ├── integration │ ├── mysql │ │ └── star_wars_introspection_test.rb │ ├── sqlite │ │ └── star_wars_introspection_test.rb │ ├── customization_test.rb │ └── schemas │ │ └── mysql.rb ├── graphql_test.rb └── test_ext.rb ├── .gitattributes ├── ext ├── .gitignore ├── extconf.h ├── extconf.rb ├── console.rb └── gql_parser.h ├── .codeclimate.yml ├── lib ├── rails-graphql.rb ├── rails │ ├── graphql.rake │ └── graphql │ │ ├── railties │ │ ├── app │ │ │ ├── base_channel.rb │ │ │ ├── base_controller.rb │ │ │ └── views │ │ │ │ ├── _fetch.js.erb │ │ │ │ └── _cable.js.erb │ │ ├── base_generator.rb │ │ └── controller_runtime.rb │ │ ├── collectors.rb │ │ ├── helpers │ │ ├── unregisterable.rb │ │ ├── instantiable.rb │ │ ├── with_global_id.rb │ │ ├── with_name.rb │ │ ├── with_namespace.rb │ │ ├── attribute_delegator.rb │ │ ├── inherited_collection │ │ │ ├── base.rb │ │ │ └── array.rb │ │ ├── with_owner.rb │ │ └── with_validator.rb │ │ ├── subscription.rb │ │ ├── alternative │ │ ├── mutation.rb │ │ ├── subscription.rb │ │ └── field_set.rb │ │ ├── subscription │ │ ├── provider.rb │ │ └── store.rb │ │ ├── version.rb │ │ ├── alternative.rb │ │ ├── type │ │ ├── scalar │ │ │ ├── any_scalar.rb │ │ │ ├── float_scalar.rb │ │ │ ├── date_time_scalar.rb │ │ │ ├── bigint_scalar.rb │ │ │ ├── date_scalar.rb │ │ │ ├── binary_scalar.rb │ │ │ ├── boolean_scalar.rb │ │ │ ├── json_scalar.rb │ │ │ ├── decimal_scalar.rb │ │ │ ├── int_scalar.rb │ │ │ ├── string_scalar.rb │ │ │ ├── id_scalar.rb │ │ │ └── time_scalar.rb │ │ ├── object │ │ │ ├── enum_value_object.rb │ │ │ ├── directive_object.rb │ │ │ ├── schema_object.rb │ │ │ ├── input_value_object.rb │ │ │ └── field_object.rb │ │ └── enum │ │ │ ├── directive_location_enum.rb │ │ │ └── type_kind_enum.rb │ │ ├── directive │ │ ├── skip_directive.rb │ │ ├── include_directive.rb │ │ ├── specified_by_directive.rb │ │ ├── cached_directive.rb │ │ └── deprecated_directive.rb │ │ ├── field │ │ ├── scoped_config.rb │ │ └── authorized_field.rb │ │ ├── request │ │ ├── strategy │ │ │ ├── sequenced_strategy.rb │ │ │ ├── multi_query_strategy.rb │ │ │ ├── dynamic_instance.rb │ │ │ └── cached_strategy.rb │ │ ├── steps │ │ │ ├── resolvable.rb │ │ │ └── preparable.rb │ │ ├── context.rb │ │ ├── arguments.rb │ │ └── errors.rb │ │ ├── helpers.rb │ │ ├── adapters │ │ └── sqlite_adapter.rb │ │ ├── collectors │ │ └── idented_collector.rb │ │ └── introspection.rb └── generators │ └── graphql │ ├── templates │ ├── schema.erb │ ├── channel.erb │ └── controller.erb │ ├── schema_generator.rb │ ├── channel_generator.rb │ ├── controller_generator.rb │ └── install_generator.rb ├── Gemfile ├── gemfiles ├── Gemfile.rails-7.0 ├── Gemfile.rails-7.1 ├── Gemfile.rails-7.2 └── Gemfile.rails-8.0 ├── bin └── console ├── tasks └── libgraphqlparser.rb ├── .gitignore ├── README.rdoc ├── benchmarks ├── append.rb ├── star_wars.rb └── star_wars │ └── original_gem.rb ├── examples ├── schema_introspection.rb └── definitions.rb ├── Rakefile ├── MIT-LICENSE ├── rails-graphql.gemspec └── CHANGELOG.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | rails-graphql.dev -------------------------------------------------------------------------------- /test/assets/introspection-db.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.lock linguist-generated 2 | -------------------------------------------------------------------------------- /ext/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | Makefile 3 | mkmf.log 4 | extconf.h 5 | -------------------------------------------------------------------------------- /ext/extconf.h: -------------------------------------------------------------------------------- 1 | #ifndef EXTCONF_H 2 | #define EXTCONF_H 3 | #endif 4 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | exclude_patterns: 3 | - 'test/' 4 | -------------------------------------------------------------------------------- /lib/rails-graphql.rb: -------------------------------------------------------------------------------- 1 | require 'gql_parser' 2 | require 'rails/graphql' 3 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .jekyll-metadata 4 | kitchen.md 5 | -------------------------------------------------------------------------------- /lib/rails/graphql.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # version 4 | # cache 5 | -------------------------------------------------------------------------------- /ext/extconf.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | 3 | create_header 4 | create_makefile 'gql_parser' 5 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /test/assets/luke.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/test/assets/luke.jpg -------------------------------------------------------------------------------- /docs/_includes/hierarchy-sub.md: -------------------------------------------------------------------------------- 1 | `↳` represents inheritance and `+` represents composition 2 | {: .fs-1 } 3 | -------------------------------------------------------------------------------- /docs/_sass/support/mixins/mixins.scss: -------------------------------------------------------------------------------- 1 | @import "./layout"; 2 | @import "./buttons"; 3 | @import "./typography"; 4 | -------------------------------------------------------------------------------- /docs/_sass/support/support.scss: -------------------------------------------------------------------------------- 1 | @import "./variables"; 2 | @import "./functions"; 3 | @import "./mixins/mixins"; 4 | -------------------------------------------------------------------------------- /docs/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/images/logo.png -------------------------------------------------------------------------------- /docs/assets/images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/images/banner.png -------------------------------------------------------------------------------- /docs/assets/images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/images/github.png -------------------------------------------------------------------------------- /docs/assets/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/images/favicon-16x16.png -------------------------------------------------------------------------------- /docs/assets/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/images/favicon-32x32.png -------------------------------------------------------------------------------- /docs/assets/images/release-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/images/release-banner.png -------------------------------------------------------------------------------- /docs/assets/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/images/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-Bk.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-Bk.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-Md.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-Md.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-Bk.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-Bk.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-BkCn.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-BkCn.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-BkCn.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-BkCn.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-BkObl.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-BkObl.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-Bold.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-Bold.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-Demi.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-Demi.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-Demi.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-Demi.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-Md.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-Md.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-MdCn.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-MdCn.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-MdCn.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-MdCn.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-MdObl.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-MdObl.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-XLt.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-XLt.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-XLt.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-XLt.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-XLtCn.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-XLtCn.woff -------------------------------------------------------------------------------- /docs/assets/images/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/images/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/assets/images/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/images/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-BkCnObl.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-BkCnObl.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-BkObl.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-BkObl.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-BoldCn.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-BoldCn.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-BoldCn.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-BoldCn.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-BoldObl.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-BoldObl.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-DemiCn.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-DemiCn.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-DemiCn.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-DemiCn.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-DemiObl.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-DemiObl.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-MdCnObl.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-MdCnObl.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-MdObl.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-MdObl.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-XLtCn.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-XLtCn.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-XLtObl.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-XLtObl.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-XLtObl.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-XLtObl.woff2 -------------------------------------------------------------------------------- /docs/_sass/utilities/utilities.scss: -------------------------------------------------------------------------------- 1 | @import "./colors"; 2 | @import "./layout"; 3 | @import "./typography"; 4 | @import "./lists"; 5 | @import "./spacing"; 6 | -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-BkCnObl.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-BkCnObl.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-BoldCnObl.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-BoldCnObl.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-BoldCnObl.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-BoldCnObl.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-BoldObl.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-BoldObl.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-DemiCnObl.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-DemiCnObl.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-DemiCnObl.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-DemiCnObl.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-DemiObl.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-DemiObl.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-MdCnObl.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-MdCnObl.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-XLtCnObl.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-XLtCnObl.woff -------------------------------------------------------------------------------- /docs/assets/fonts/ITCAvantGardeStd-XLtCnObl.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtualshield/rails-graphql/HEAD/docs/assets/fonts/ITCAvantGardeStd-XLtCnObl.woff2 -------------------------------------------------------------------------------- /docs/robots.txt: -------------------------------------------------------------------------------- 1 | --- 2 | layout: null 3 | sitemap: false 4 | --- 5 | 6 | User-agent: * 7 | Disallow: 8 | 9 | Sitemap: {{ 'sitemap.xml' | absolute_url }} 10 | -------------------------------------------------------------------------------- /docs/_includes/table-of-contents.html: -------------------------------------------------------------------------------- 1 |
2 | Table of Contents 3 | {% include toc.html html=content h_min=2 sanitize=true %} 4 |
5 | -------------------------------------------------------------------------------- /lib/generators/graphql/templates/schema.erb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module GraphQL 4 | <%- module_namespacing do -%> 5 | class <%= schema_name %> < GraphQL::Schema 6 | end 7 | <%- end -%> 8 | end 9 | -------------------------------------------------------------------------------- /lib/generators/graphql/templates/channel.erb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | <% module_namespacing do -%> 4 | class <%= channel_name %> < ApplicationCable::Channel 5 | include GraphQL::Channel 6 | end 7 | <% end -%> 8 | -------------------------------------------------------------------------------- /lib/generators/graphql/templates/controller.erb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | <% module_namespacing do -%> 4 | class <%= controller_name %> < ApplicationController 5 | include GraphQL::Controller 6 | end 7 | <% end -%> 8 | -------------------------------------------------------------------------------- /docs/_sass/support/_functions.scss: -------------------------------------------------------------------------------- 1 | @function rem($size, $unit: "") { 2 | $remSize: $size / $root-font-size; 3 | 4 | @if ($unit == false) { 5 | @return #{$remSize}; 6 | } @else { 7 | @return #{$remSize}rem; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /docs/_layouts/table-wrappers.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: vendor/compress 3 | --- 4 | 5 | {% assign content_ = content | replace: '', '' %} 7 | {{ content_ }} 8 | -------------------------------------------------------------------------------- /docs/_sass/mermaid.scss: -------------------------------------------------------------------------------- 1 | // Customize the diagrams colors 2 | div.mermaid { 3 | // Regular box 4 | .node rect, 5 | .node circle, 6 | .node ellipse, 7 | .node polygon, 8 | .node path { 9 | fill: $red-000 !important; 10 | stroke: $red-200 !important; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docs/assets/images/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /docs/assets/js/site.js: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | {% include_relative lunr.min.js %} 5 | {% comment %} 6 | {% include_relative vendor/mermaid.min.js %} 7 | {% endcomment %} 8 | 9 | {% include_relative events.js %} 10 | {% include_relative code.js %} 11 | {% include_relative search.js %} 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source 'https://rubygems.org' 3 | 4 | gemspec 5 | 6 | gem 'rails', '~> 7.1' 7 | gem 'bootsnap' # required by the Rails apps generated in tests 8 | gem 'ruby-prof', platform: :ruby 9 | gem 'pry-byebug' 10 | 11 | gem 'flamegraph' 12 | gem 'stackprof' 13 | -------------------------------------------------------------------------------- /lib/rails/graphql/railties/app/base_channel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module GraphQL 4 | base = ActionCable::Channel::Base 5 | base = ApplicationCable::Channel if defined?(ApplicationCable::Channel) 6 | 7 | BaseChannel = Class.new(base) do 8 | include ::Rails::GraphQL::Channel 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /gemfiles/Gemfile.rails-7.0: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source 'https://rubygems.org' 3 | 4 | gemspec path: "../" 5 | 6 | gem 'rails', '~> 7.0.0' 7 | gem 'bootsnap' # required by the Rails apps generated in tests 8 | gem 'ruby-prof', platform: :ruby 9 | gem 'pry-byebug' 10 | 11 | gem 'flamegraph' 12 | gem 'stackprof' 13 | -------------------------------------------------------------------------------- /gemfiles/Gemfile.rails-7.1: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source 'https://rubygems.org' 3 | 4 | gemspec path: "../" 5 | 6 | gem 'rails', '~> 7.1.0' 7 | gem 'bootsnap' # required by the Rails apps generated in tests 8 | gem 'ruby-prof', platform: :ruby 9 | gem 'pry-byebug' 10 | 11 | gem 'flamegraph' 12 | gem 'stackprof' 13 | -------------------------------------------------------------------------------- /gemfiles/Gemfile.rails-7.2: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source 'https://rubygems.org' 3 | 4 | gemspec path: "../" 5 | 6 | gem 'rails', '~> 7.2.0' 7 | gem 'bootsnap' # required by the Rails apps generated in tests 8 | gem 'ruby-prof', platform: :ruby 9 | gem 'pry-byebug' 10 | 11 | gem 'flamegraph' 12 | gem 'stackprof' 13 | -------------------------------------------------------------------------------- /docs/_includes/installation.md: -------------------------------------------------------------------------------- 1 | To install rails-graphql you need to add the following to your **Gemfile**: 2 | ```ruby 3 | gem 'rails-graphql', '~> 1.0' 4 | ``` 5 | 6 | Also, run: 7 | 8 | ```bash 9 | $ bundle 10 | ``` 11 | 12 | Or, for non-Gemfile related usage, simply: 13 | 14 | ```bash 15 | gem install rails-graphql 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/_includes/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/_includes/tracking.html: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /gemfiles/Gemfile.rails-8.0: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source 'https://rubygems.org' 3 | 4 | gemspec path: "../" 5 | 6 | gem 'rails', '~> 8.0.0' 7 | gem 'bootsnap' # required by the Rails apps generated in tests 8 | gem 'ruby-prof', platform: :ruby 9 | gem 'pry-byebug' 10 | gem 'sqlite3' 11 | 12 | gem 'flamegraph' 13 | gem 'stackprof' 14 | -------------------------------------------------------------------------------- /ext/console.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | require 'pry' 5 | require 'irb' 6 | 7 | puts 'Pre making' 8 | system 'ruby gql_parser.rb' 9 | puts 'Making' 10 | system 'make' 11 | 12 | puts 'Requiring' 13 | require_relative './gql_parser.so' 14 | 15 | # puts GQLParser.parse_execution("123") 16 | 17 | ARGV.clear 18 | IRB.start 19 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | banner: true 4 | toc: false 5 | image: "/assets/images/banner.png" 6 | --- 7 | 8 | #### License 9 | 10 | Copyright © 2020-2023 VirtualShield LLC. See 11 | 12 | The MIT License 13 | 14 | for further details. 15 | -------------------------------------------------------------------------------- /lib/rails/graphql/railties/app/base_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module GraphQL 4 | base = ActionController::Base 5 | base = ApplicationController if defined?(ApplicationController) 6 | 7 | BaseController = Class.new(base) do 8 | include ::Rails::GraphQL::Controller 9 | 10 | skip_before_action :verify_authenticity_token 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | $LOAD_PATH.unshift Pathname.new(__dir__).join('../lib') 5 | 6 | require 'debug' 7 | require 'irb' 8 | require 'rails' 9 | require 'irb/completion' 10 | 11 | require 'rails-graphql' 12 | 13 | class ApplicationSchema < GraphQL::Schema 14 | end 15 | 16 | $config = Rails::GraphQL.config 17 | 18 | ARGV.clear 19 | IRB.start 20 | -------------------------------------------------------------------------------- /lib/rails/graphql/collectors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # All the possible collectors that uses the reverse visit approach 6 | module Collectors 7 | extend ActiveSupport::Autoload 8 | 9 | autoload :HashCollector 10 | autoload :IdentedCollector 11 | autoload :JsonCollector 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /docs/_sass/utilities/_lists.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Utility classes for lists 3 | // 4 | 5 | // stylelint-disable primer/selector-no-utility, primer/no-override, selector-max-type 6 | 7 | .list-style-none { 8 | padding: 0 !important; 9 | margin: 0 !important; 10 | list-style: none !important; 11 | 12 | li { 13 | &::before { 14 | display: none !important; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tasks/libgraphqlparser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rake/extensiontask' 4 | 5 | gem_spec = Gem::Specification.load('rails-graphql.gemspec') 6 | Rake::ExtensionTask.new(:gql_parser, gem_spec) do |ext| 7 | ext.name = 'gql_parser' 8 | ext.ext_dir = 'ext' 9 | ext.lib_dir = 'lib' 10 | ext.cross_compile = true 11 | ext.cross_platform = %w[x86-mingw32 x64-mingw32] 12 | end 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | *.gem 3 | *.rbc 4 | *.so 5 | *.bundle 6 | .bundle 7 | .config 8 | .yardoc 9 | .byebug_history 10 | .versions.conf 11 | .idea 12 | coverage 13 | doc/ 14 | .config 15 | coverage/ 16 | InstalledFiles 17 | pkg/ 18 | rdoc/ 19 | spec/reports/ 20 | spec/examples.txt 21 | test/tmp/ 22 | test/version_tmp/ 23 | tmp/ 24 | .ruby-version 25 | .ruby-gemset 26 | .ruby-env 27 | gemfiles/*.lock 28 | vendor/ 29 | .vscode 30 | -------------------------------------------------------------------------------- /docs/handbook/snippets.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Snippets - Handbook 4 | description: Some ready to copy-and-paste snippets to use 5 | --- 6 | 7 | # Snippets 8 | 9 | Here is a series of snippets that you can use however you see fit. 10 | 11 | {: .important } 12 | > **Important** 13 | > This section is under construction. More snippets will be be added. 14 | 15 | {% include_relative snippets/devise-jwt.html %} 16 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Torque PostgreSQL 2 | 3 | 4 | == Download and installation 5 | 6 | The latest version of Rails GraphQL can be installed with RubyGems: 7 | 8 | $ gem install rails-graphql 9 | 10 | Source code can be downloaded direct from the GitHub repository: 11 | 12 | * https://github.com/virtualshield/rails-graphql 13 | 14 | 15 | == License 16 | 17 | Rails GraphQL is released under the MIT license: 18 | 19 | * http://www.opensource.org/licenses/MIT 20 | -------------------------------------------------------------------------------- /docs/_sass/modules.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Import external dependencies 3 | // 4 | @import "./vendor/normalize.scss/normalize.scss"; 5 | 6 | // 7 | // Modules 8 | // 9 | @import "./base"; 10 | @import "./layout"; 11 | @import "./content"; 12 | @import "./navigation"; 13 | @import "./typography"; 14 | @import "./labels"; 15 | @import "./buttons"; 16 | @import "./search"; 17 | @import "./tables"; 18 | @import "./code"; 19 | @import "./utilities/utilities"; 20 | @import "./print"; 21 | -------------------------------------------------------------------------------- /docs/_includes/type-creators.md: -------------------------------------------------------------------------------- 1 | ### For gem Creators 2 | 3 | Once you have created your {{ include.type }}s in your gem, remember to add them into 4 | [`config.known_dependencies`](/handbook/settings#known_dependencies). 5 | It is not recommended to `require` such files in your gem. 6 | 7 | ```ruby 8 | Rails::GraphQL.config.known_dependencies[:{{ include.type }}].update( 9 | my_gem_{{ include.type }}: "#{__dir__}/{{ include.type }}s/my_gem_{{ include.type }}", 10 | ) 11 | ``` 12 | -------------------------------------------------------------------------------- /lib/rails/graphql/helpers/unregisterable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | module Helpers 6 | # Helper that allows unregisterable objects to be both identified and 7 | # removed from type map 8 | module Unregisterable 9 | # Simply remove itself from the type map 10 | def unregister! 11 | GraphQL.type_map.unregister(self) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/rails/graphql/helpers/instantiable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | module Helpers 6 | # This marks an object as instantiable during a requesting, which means it 7 | # will be associated with an event and most of it's information comes from 8 | # the running event. 9 | module Instantiable 10 | delegate_missing_to :event 11 | attr_reader :event 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/rails/graphql/subscription.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # = GraphQL Subscription 6 | # 7 | # A namespace for storing subscription-related objects like the provider 8 | # for a stream/websocket provider, and the store, for where the 9 | # subscriptions are stored 10 | module Subscription 11 | extend ActiveSupport::Autoload 12 | 13 | autoload :Store 14 | autoload :Provider 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rails/graphql/alternative/mutation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # = GraphQL Alternative Mutation 6 | # 7 | # Same as it's parent class, but for mutations 8 | class Alternative::Mutation < Alternative::Query 9 | redefine_singleton_method(:type_field_class) { :mutation } 10 | self.abstract = true 11 | 12 | class << self 13 | delegate :perform, to: :@field, allow_nil: true 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | toc: false 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /lib/rails/graphql/subscription/provider.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | module Subscription 6 | # = GraphQL Subscription Provider 7 | # 8 | # Subscription provider holds all the possible options for handlers of 9 | # subscriptions, which all should inherit from Provider::Base 10 | module Provider 11 | extend ActiveSupport::Autoload 12 | 13 | autoload :Base 14 | autoload :ActionCable 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/rails/graphql/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # Returns the currently loaded version of the GraphQL as a Gem::Version. 6 | def self.gem_version 7 | Gem::Version.new(version) 8 | end 9 | 10 | def self.version 11 | VERSION::STRING 12 | end 13 | 14 | module VERSION 15 | MAJOR = 1 16 | MINOR = 0 17 | TINY = 6 18 | PRE = nil 19 | 20 | STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/rails/graphql/subscription/store.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | module Subscription 6 | # = GraphQL Subscription Store 7 | # 8 | # Subscription store holds all the possible options for storing the 9 | # subscriptions, allowing to segmentation by field, variables, and several 10 | # other things according to the necessity 11 | module Store 12 | extend ActiveSupport::Autoload 13 | 14 | autoload :Base 15 | autoload :Memory 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /docs/_includes/banner.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /lib/rails/graphql/alternative/subscription.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # = GraphQL Alternative Subscription 6 | # 7 | # Same as it's parent class, but for subscription 8 | class Alternative::Subscription < Alternative::Query 9 | redefine_singleton_method(:type_field_class) { :subscription } 10 | self.abstract = true 11 | 12 | class << self 13 | delegate :scope, :trigger_for, :trigger, :unsubscribe_from, :unsubscribe, 14 | to: :@field, allow_nil: true 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/string_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_StringScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::StringScalar 5 | 6 | def test_as_json 7 | assert_equal('1', DESCRIBED_CLASS.as_json(1)) 8 | assert_equal('', DESCRIBED_CLASS.as_json(nil)) 9 | assert_equal('a', DESCRIBED_CLASS.as_json('a')) 10 | 11 | str = 'Sample'.encode(Encoding::ISO_8859_1) 12 | result = DESCRIBED_CLASS.as_json(str) 13 | 14 | assert_equal('Sample', result) 15 | assert_equal(result.encoding, Encoding::UTF_8) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /benchmarks/append.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark/ips' 2 | 3 | def slow_plus 4 | x = 'foo' 5 | x += 'bar' 6 | end 7 | 8 | def slow_concat 9 | x = 'foo' 10 | x.concat 'bar' 11 | end 12 | 13 | def fast_append 14 | x = 'foo' 15 | x << 'bar' 16 | end 17 | 18 | def fast_interpolation 19 | x = 'foo' 20 | x = "#{x}bar" 21 | end 22 | 23 | Benchmark.ips do |x| 24 | x.report('x += "bar"') { slow_plus } 25 | x.report('x.concat("bar")') { slow_concat } 26 | x.report('x << "bar"') { fast_append } 27 | x.report('x = "#{x}#{\'bar\'}"') { fast_interpolation } 28 | x.compare! 29 | end 30 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/binary_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_BinaryScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::BinaryScalar 5 | FILE_PATH = Pathname.new(__dir__).join('../../../assets/luke.jpg') 6 | 7 | def test_as_json 8 | assert_equal('MQ==', DESCRIBED_CLASS.as_json(1)) 9 | assert_equal('YQ==', DESCRIBED_CLASS.as_json('a')) 10 | assert_equal('', DESCRIBED_CLASS.as_json(nil)) 11 | end 12 | 13 | def test_deserialize 14 | file = DESCRIBED_CLASS.deserialize(File.read(FILE_PATH)) 15 | assert_kind_of(ActiveModel::Type::Binary::Data, file) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/float_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_FloatScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::FloatScalar 5 | 6 | def test_valid_input_ask 7 | assert(DESCRIBED_CLASS.valid_input?(1.0)) 8 | 9 | refute(DESCRIBED_CLASS.valid_input?('12')) 10 | refute(DESCRIBED_CLASS.valid_input?(10)) 11 | end 12 | 13 | def test_valid_output_ask 14 | assert(DESCRIBED_CLASS.valid_output?(1.0)) 15 | 16 | refute(DESCRIBED_CLASS.valid_input?(false)) 17 | end 18 | 19 | def test_as_json_ask 20 | assert_kind_of(Float, DESCRIBED_CLASS.as_json(10)) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/rails/graphql/alternative.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # Several alternatives to declare GraphQL objects 6 | module Alternative 7 | extend ActiveSupport::Autoload 8 | 9 | autoload :Query 10 | autoload :Mutation 11 | autoload :Subscription 12 | 13 | autoload_at "#{__dir__}/alternative/query" do 14 | autoload :Field 15 | end 16 | 17 | autoload_at "#{__dir__}/alternative/field_set" do 18 | autoload :FieldSet 19 | autoload :QuerySet 20 | autoload :MutationSet 21 | autoload :SubscriptionSet 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /docs/_includes/type-description.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | Allows documenting {{ include.type }}s. This value can be retrieved using [introspection](/guides/introspection) 4 | or during a [to_gql](/guides/customizing/controller#describe) output. Within the class, `desc` 5 | works as a syntax sugar for `self.description = ''`. It also supports descriptions from 6 | [I18n](/guides/i18n). 7 | 8 | ```ruby 9 | # app/graphql/{{ include.type }}s/{% if include.file %}{{ include.file }}{% else %}{{ include.name | downcase }}{% endif %}.rb 10 | module GraphQL 11 | class {{ include.name }} < GraphQL::{{ include.type | capitalize }} 12 | desc 'This is awesome!' 13 | end 14 | end 15 | ``` 16 | -------------------------------------------------------------------------------- /lib/rails/graphql/railties/app/views/_fetch.js.erb: -------------------------------------------------------------------------------- 1 | function graphQLFetcher(graphQLParams) { 2 | // This example expects a GraphQL server at the path /graphql. 3 | // Change this to point wherever you host your GraphQL server. 4 | return fetch('<%= url %>', { 5 | method: 'post', 6 | headers: { 7 | 'Accept': 'application/json', 8 | 'Content-Type': 'application/json' 9 | }, 10 | body: JSON.stringify(graphQLParams), 11 | }).then(function (response) { 12 | return response.text(); 13 | }).then(function (responseBody) { 14 | try { 15 | return JSON.parse(responseBody); 16 | } catch (error) { 17 | return responseBody; 18 | } 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /test/assets/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | graphql: 3 | sample_a: A 4 | translate: 5 | sample_b: B 6 | field: 7 | sample_c: C 8 | schema: 9 | sample_field: Field 10 | sample_d: D 11 | argument: 12 | sample_field: 13 | arg_sample: Argument 14 | interface: 15 | interface_desc: Interface 16 | enum: 17 | enum_desc: Enum 18 | object: 19 | object_desc: Object 20 | union: 21 | union_desc: Union 22 | input_object: 23 | input_desc_input: Input 24 | scalar: 25 | scalar_desc: Scalar 26 | field: 27 | sample_e: E 28 | schema: 29 | sample_f: F 30 | -------------------------------------------------------------------------------- /lib/rails/graphql/helpers/with_global_id.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | module Helpers 6 | # Helper module that is a different implementation of the 7 | # +GlobalID::Identification+, but instead of things being found by the 8 | # class that they are, it uses owners and base classes. 9 | module WithGlobalID 10 | def to_global_id(options = nil) # :nodoc: 11 | GlobalID.create(self, options) 12 | end 13 | 14 | alias to_gid to_global_id 15 | 16 | def to_gid_param(options = nil) # :nodoc: 17 | to_global_id(options).to_param 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/json_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_JsonScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::JsonScalar 5 | 6 | def test_valid_input_ask 7 | assert(DESCRIBED_CLASS.valid_input?({})) 8 | refute(DESCRIBED_CLASS.valid_input?(1)) 9 | end 10 | 11 | def test_valid_output_ask 12 | assert(DESCRIBED_CLASS.valid_output?({})) 13 | refute(DESCRIBED_CLASS.valid_output?(1)) 14 | end 15 | 16 | def test_to_json 17 | assert_equal('{"a":1}', DESCRIBED_CLASS.to_json({ a: 1 })) 18 | end 19 | 20 | def test_as_json 21 | assert_equal({ 'a' => 1 }, DESCRIBED_CLASS.as_json({ a: 1 })) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/any_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # Handles any type of data for both input and output 7 | class Scalar::AnyScalar < Scalar 8 | desc 'The Any scalar type allows anything for both input and output.' 9 | 10 | class << self 11 | def valid_input?(value) 12 | true 13 | end 14 | 15 | def valid_output?(value) 16 | true 17 | end 18 | 19 | def to_json(value) 20 | value.to_json 21 | end 22 | 23 | def as_json(value) 24 | value.as_json 25 | end 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /docs/_sass/labels.scss: -------------------------------------------------------------------------------- 1 | // Labels (not the form kind) 2 | 3 | .label, 4 | .label-red { 5 | display: inline-block; 6 | padding-top: 0.16em; 7 | padding-right: 0.56em; 8 | padding-bottom: 0.16em; 9 | padding-left: 0.56em; 10 | margin-right: $sp-2; 11 | margin-left: $sp-2; 12 | color: $white; 13 | text-transform: uppercase; 14 | vertical-align: middle; 15 | background-color: $red-200; 16 | @include fs-2; 17 | border-radius: 24px; 18 | } 19 | 20 | .label-green { 21 | background-color: $green-200; 22 | } 23 | 24 | .label-purple { 25 | background-color: $purple-200; 26 | } 27 | 28 | .label-blue { 29 | background-color: $blue-200; 30 | } 31 | 32 | .label-yellow { 33 | color: $grey-dk-200; 34 | background-color: $yellow-200; 35 | } 36 | -------------------------------------------------------------------------------- /lib/rails/graphql/directive/skip_directive.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # = GraphQL Spec Skip Directive 6 | # 7 | # Allow skipping fields given an +if+ condition 8 | class Directive::SkipDirective < Directive 9 | self.spec_object = true 10 | 11 | desc 'Allows for conditional exclusion during execution as described by the if argument.' 12 | 13 | placed_on :field, :fragment_spread, :inline_fragment 14 | 15 | argument :if, :boolean, null: false, desc: <<~DESC 16 | When true, the underlying element will be automatically marked as null. 17 | DESC 18 | 19 | on(:attach) do |source| 20 | source.skip! if args[:if] 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /docs/assets/css/site.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | // Import external dependencies 5 | @import "./normalize"; 6 | 7 | // Setup settings 8 | @import "./fonts"; 9 | @import "./mixins"; 10 | @import "./support/support"; 11 | @import "./variables"; 12 | 13 | // Load parts of the original style 14 | @import "./base"; 15 | @import "./layout"; 16 | @import "./content"; 17 | @import "./typography"; 18 | @import "./labels"; 19 | @import "./buttons"; 20 | @import "./search"; 21 | @import "./tables"; 22 | @import "./code"; 23 | @import "./mermaid"; 24 | @import "./utilities/utilities"; 25 | @import "./print"; 26 | @import "./callouts"; 27 | 28 | // Load dark mode 29 | body.dark { @import "./dark"; } 30 | @media (prefers-color-scheme: dark) { @import "./dark"; } 31 | 32 | @import "./overrides"; 33 | -------------------------------------------------------------------------------- /docs/_sass/mixins.scss: -------------------------------------------------------------------------------- 1 | // Colored button 2 | 3 | @mixin colorize-button($fg, $bg1, $bg2) { 4 | color: $fg; 5 | background-color: $bg1; 6 | background-image: linear-gradient($bg1, $bg2); 7 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), 0 4px 10px rgba(0, 0, 0, 0.12); 8 | 9 | &:hover, 10 | &.zeroclipboard-is-hover { 11 | color: $fg; 12 | background-color: $bg1; 13 | background-image: linear-gradient($bg1, $bg2); 14 | box-shadow: inset 0 0 5px 2px rgba(0, 0, 0, 0.3); 15 | } 16 | 17 | &:active, 18 | &.selected, 19 | &.zeroclipboard-is-active { 20 | background-color: $bg1; 21 | background-image: none; 22 | box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15); 23 | } 24 | 25 | &.selected:hover { 26 | background-color: $bg1; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/schema_introspection.rb: -------------------------------------------------------------------------------- 1 | # This reproduces the same result as 2 | # http://spec.graphql.org/June2018/#sec-Schema-Introspection 3 | 4 | [ 5 | GraphQL::Object::SchemaObject, 6 | GraphQL::Object::TypeObject, 7 | GraphQL::Object::FieldObject, 8 | GraphQL::Object::InputValueObject, 9 | GraphQL::Object::EnumValueObject, 10 | GraphQL::Enum::TypeKindEnum, 11 | GraphQL::Object::DirectiveObject, 12 | GraphQL::Enum::DirectiveLocationEnum, 13 | ].each_with_index do |klass, i| 14 | puts if i > 0 15 | puts GraphQL.to_gql(klass, with_descriptions: false) 16 | end 17 | 18 | # This displays all the capabilities of the SampleSchema 19 | 20 | class SampleSchema < GraphQL::Schema 21 | end 22 | 23 | Rails::GraphQL.eager_load! 24 | puts SampleSchema.to_gql(with_descriptions: false) 25 | -------------------------------------------------------------------------------- /lib/rails/graphql/directive/include_directive.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # = GraphQL Spec Include Directive 6 | # 7 | # Allow including fields only +if+ condition is true 8 | class Directive::IncludeDirective < Directive 9 | self.spec_object = true 10 | 11 | placed_on :field, :fragment_spread, :inline_fragment 12 | 13 | desc 'Allows for conditional inclusion during execution as described by the if argument.' 14 | 15 | argument :if, :boolean, null: false, desc: <<~DESC 16 | When false, the underlying element will be automatically marked as null. 17 | DESC 18 | 19 | on(:attach) do |source| 20 | source.skip! unless args[:if] 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/generators/graphql/schema_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails/generators/base' 4 | 5 | module GraphQL 6 | module Generators 7 | class SchemaGenerator < Rails::Generators::Base # :nodoc: 8 | include Rails::GraphQL::BaseGenerator 9 | 10 | desc 'Add a new GraphQL schema' 11 | 12 | argument :schema, type: :string, optional: true, 13 | default: "#{APP_MODULE_NAME}Schema", 14 | desc: 'A name for the schema' 15 | 16 | def create_schema_file 17 | template 'schema.erb', "#{options[:directory]}/#{schema_name.underscore}.rb" 18 | end 19 | 20 | private 21 | 22 | def schema_name 23 | @schema_name ||= options.fetch(:schema, "#{APP_MODULE_NAME}Schema") 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /docs/_sass/support/mixins/_buttons.scss: -------------------------------------------------------------------------------- 1 | // Colored button 2 | 3 | @mixin btn-color($fg, $bg) { 4 | color: $fg; 5 | background-color: darken($bg, 2%); 6 | background-image: linear-gradient(lighten($bg, 5%), darken($bg, 2%)); 7 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), 0 4px 10px rgba(0, 0, 0, 0.12); 8 | 9 | &:hover, 10 | &.zeroclipboard-is-hover { 11 | color: $fg; 12 | background-color: darken($bg, 4%); 13 | background-image: linear-gradient((lighten($bg, 2%), darken($bg, 4%))); 14 | } 15 | 16 | &:active, 17 | &.selected, 18 | &.zeroclipboard-is-active { 19 | background-color: darken($bg, 5%); 20 | background-image: none; 21 | box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15); 22 | } 23 | 24 | &.selected:hover { 25 | background-color: darken($bg, 10%); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/generators/graphql/channel_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails/generators/base' 4 | 5 | module GraphQL 6 | module Generators 7 | class ChannelGenerator < Rails::Generators::Base # :nodoc: 8 | include Rails::GraphQL::BaseGenerator 9 | 10 | desc 'Add a new action cable channel that operates with GraphQL' 11 | 12 | argument :name, type: :string, optional: true, 13 | default: "GraphQLChannel", 14 | desc: 'The name for the channel' 15 | 16 | def create_channel_file 17 | template 'channel.erb', "app/channels/#{channel_name.underscore}.rb" 18 | end 19 | 20 | private 21 | 22 | def channel_name 23 | @channel_name ||= options.fetch(:name, 'GraphQLChannel').classify 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/rails/graphql/directive/specified_by_directive.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # = GraphQL Spec Specified By Directive 6 | # 7 | # Provides a scalar specification URL for specifying the behavior of 8 | # custom scalar types. 9 | class Directive::SpecifiedByDirective < Directive 10 | self.spec_object = true 11 | 12 | placed_on :scalar 13 | 14 | desc <<~DESC 15 | A built-in directive used within the type system definition language to provide 16 | a scalar specification URL for specifying the behavior of custom scalar types. 17 | DESC 18 | 19 | argument :url, :string, null: false, desc: <<~DESC 20 | Point to a human-readable specification of the data format. 21 | DESC 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Rake tasks for development purposes 3 | 4 | begin 5 | require 'bundler/setup' unless ENV.key?('CI_COMPILE') 6 | rescue LoadError 7 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 8 | end 9 | 10 | require 'rdoc/task' 11 | require 'rake/testtask' 12 | 13 | require_relative 'tasks/libgraphqlparser' 14 | 15 | task default: :test 16 | 17 | Rake::TestTask.new(:test) do |t| 18 | t.libs << 'test' 19 | t.warning = true 20 | t.verbose = true 21 | t.test_files = Dir.glob("test/**/*_test.rb") 22 | end 23 | 24 | RDoc::Task.new(:rdoc) do |rdoc| 25 | rdoc.rdoc_dir = 'rdoc' 26 | rdoc.title = 'Rails::GraphQL' 27 | rdoc.options << '--line-numbers' 28 | rdoc.rdoc_files.include('README.rdoc') 29 | rdoc.rdoc_files.include('lib/**/*.rb') 30 | end 31 | 32 | -------------------------------------------------------------------------------- /test/graphql/request/component/operation_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Request_Component_OperationTest < GraphQL::TestCase 4 | def test_build 5 | request = Object.new 6 | request.define_singleton_method(:build) { |klass, *| klass } 7 | 8 | assert_equal(klass::Query, klass.build(request, new_token('', :query))) 9 | assert_equal(klass::Mutation, klass.build(request, new_token('', :mutation))) 10 | assert_equal(klass::Subscription, klass.build(request, new_token('', :subscription))) 11 | 12 | assert_raises(Rails::GraphQL::NameError) { klass.build(request, new_token('', '')) } 13 | assert_raises(Rails::GraphQL::NameError) { klass.build(request, new_token('', :field)) } 14 | end 15 | 16 | private 17 | 18 | def klass 19 | Rails::GraphQL::Request::Component::Operation 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/generators/graphql/controller_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails/generators/base' 4 | 5 | module GraphQL 6 | module Generators 7 | class ControllerGenerator < Rails::Generators::Base # :nodoc: 8 | include Rails::GraphQL::BaseGenerator 9 | 10 | desc 'Add a new controller that operates with GraphQL' 11 | 12 | argument :name, type: :string, optional: true, 13 | default: "GraphQLController", 14 | desc: 'The name for the controller' 15 | 16 | def create_controller_file 17 | template 'controller.erb', "app/controllers/#{controller_name.underscore}.rb" 18 | end 19 | 20 | private 21 | 22 | def controller_name 23 | @controller_name ||= options.fetch(:name, 'GraphQLController').classify 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/object/enum_value_object.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # The introspection object for an enum value 7 | class Object::EnumValueObject < Object 8 | self.spec_object = true 9 | 10 | rename! '__EnumValue' 11 | 12 | desc <<~DESC 13 | One of the values of an Enum object. It is unique within the Enum set 14 | of values. It's a string representation, not a numeric representation, 15 | of a value kept as all caps (ie. ONE_VALUE). 16 | DESC 17 | 18 | field :name, :string, null: false 19 | field :description, :string 20 | field :is_deprecated, :boolean, null: false 21 | field :deprecation_reason, :string 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/id_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_IdScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::IdScalar 5 | 6 | def test_valid_input_ask 7 | assert(DESCRIBED_CLASS.valid_input?(12345)) 8 | assert(DESCRIBED_CLASS.valid_input?('bt3btb3t3')) 9 | assert(DESCRIBED_CLASS.valid_input?(SecureRandom.uuid)) 10 | 11 | refute(DESCRIBED_CLASS.valid_input?(12.0)) 12 | end 13 | 14 | def test_as_json 15 | str = 'Sample'.encode(Encoding::ISO_8859_1) 16 | result = DESCRIBED_CLASS.as_json(str) 17 | 18 | assert_equal('Sample', result) 19 | assert_equal(Encoding::UTF_8, result.encoding) 20 | end 21 | 22 | def test_deserialize 23 | assert_kind_of(String, DESCRIBED_CLASS.deserialize('foo')) 24 | assert_equal('foo', DESCRIBED_CLASS.deserialize('foo')) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/rails/graphql/railties/base_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # = GraphQL Base Generator 6 | # 7 | # A module to help generators to operate 8 | module BaseGenerator 9 | TEMPALTES_PATH = '../../../generators/graphql/templates' 10 | APP_MODULE_NAME = Rails.application.class.name.chomp('::Application') 11 | 12 | def self.included(base) 13 | base.const_set(:APP_MODULE_NAME, APP_MODULE_NAME) 14 | base.send(:namespace, "graphql:#{base.name.demodulize.underscore[0..-11]}") 15 | base.send(:source_root, File.expand_path(TEMPALTES_PATH, __dir__)) 16 | base.send(:class_option, :directory, type: :string, 17 | default: 'app/graphql', 18 | desc: 'Directory where generated files should be saved', 19 | ) 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /docs/_sass/print.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable selector-max-specificity, selector-max-id, selector-max-type, selector-no-qualifying-type, primer/no-override, 2 | 3 | @media print { 4 | .site-footer, 5 | .site-button, 6 | #edit-this-page, 7 | #back-to-top, 8 | .site-nav, 9 | .main-header { 10 | display: none !important; 11 | } 12 | 13 | .side-bar { 14 | width: 100%; 15 | height: auto; 16 | border-right: 0 !important; 17 | } 18 | 19 | .site-header { 20 | border-bottom: 1px solid $border-color; 21 | } 22 | 23 | .site-title { 24 | font-size: $root-font-size !important; 25 | font-weight: 700 !important; 26 | } 27 | 28 | .text-small { 29 | font-size: 8pt !important; 30 | } 31 | 32 | pre.highlight { 33 | border: 1px solid $border-color; 34 | } 35 | 36 | .main { 37 | max-width: none; 38 | margin-left: 0; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/decimal_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_DecimalScalarScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::DecimalScalar 5 | 6 | def test_valid_input_ask 7 | assert(DESCRIBED_CLASS.valid_input?('1.0')) 8 | assert(DESCRIBED_CLASS.valid_input?('10.0')) 9 | assert(DESCRIBED_CLASS.valid_input?('10.00')) 10 | assert(DESCRIBED_CLASS.valid_input?('100.000')) 11 | 12 | refute(DESCRIBED_CLASS.valid_input?(10)) 13 | end 14 | 15 | def test_valid_output_ask 16 | assert(DESCRIBED_CLASS.valid_output?(10.0)) 17 | 18 | refute(DESCRIBED_CLASS.valid_output?(false)) 19 | end 20 | 21 | def test_as_json 22 | assert_equal('10.0', DESCRIBED_CLASS.as_json(10)) 23 | end 24 | 25 | def test_deserialize 26 | assert_kind_of(BigDecimal, DESCRIBED_CLASS.deserialize(1.0)) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/int_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_IntScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::IntScalar 5 | 6 | def test_valid_input_ask 7 | assert(DESCRIBED_CLASS.valid_input?(12345)) 8 | assert(DESCRIBED_CLASS.valid_input?(2147483647)) 9 | 10 | refute(DESCRIBED_CLASS.valid_input?(12.0)) 11 | refute(DESCRIBED_CLASS.valid_input?(2147483649)) 12 | refute(DESCRIBED_CLASS.valid_input?('2147483649')) 13 | end 14 | 15 | def test_valid_output_ask 16 | assert(DESCRIBED_CLASS.valid_output?(12345)) 17 | 18 | refute(DESCRIBED_CLASS.valid_output?(2147483649)) 19 | refute(DESCRIBED_CLASS.valid_output?('2147483649')) 20 | end 21 | 22 | def test_as_json 23 | assert_equal(123, DESCRIBED_CLASS.as_json(123)) 24 | assert_nil(nil, DESCRIBED_CLASS.as_json(2147483649)) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /docs/_sass/support/mixins/_layout.scss: -------------------------------------------------------------------------------- 1 | // Media query 2 | 3 | // Media query mixin 4 | // Usage: 5 | // @include mq(md) { 6 | // ..medium and up styles 7 | // } 8 | @mixin mq($name) { 9 | // Retrieves the value from the key 10 | $value: map-get($media-queries, $name); 11 | 12 | // If the key exists in the map 13 | @if $value != null { 14 | // Prints a media query based on the value 15 | @media (min-width: rem($value)) { 16 | @content; 17 | } 18 | } @else { 19 | @warn "No value could be retrieved from `#{$media-query}`. " 20 | + "Please make sure it is defined in `$media-queries` map."; 21 | } 22 | } 23 | 24 | // Responsive container 25 | 26 | @mixin container { 27 | padding-right: $gutter-spacing-sm; 28 | padding-left: $gutter-spacing-sm; 29 | 30 | @include mq(md) { 31 | padding-right: $gutter-spacing; 32 | padding-left: $gutter-spacing; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/rails/graphql/field/scoped_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # Helper class to be used while configuring a field using a block. An 6 | # instance of this class works as proxy for changes to the actual field. 7 | class Field::ScopedConfig < Struct.new(:field, :receiver) 8 | delegate :argument, :ref_argument, :id_argument, :use, :internal?, :disabled?, 9 | :enabled?, :disable!, :enable!, :authorize, to: :field 10 | 11 | delegate_missing_to :receiver 12 | 13 | def rename!(name) 14 | field.instance_variable_set(:@gql_name, name.to_s) 15 | end 16 | 17 | def method_name(value) 18 | field.instance_variable_set(:@method_name, value.to_sym) 19 | end 20 | 21 | def desc(value) 22 | field.description = value 23 | end 24 | 25 | alias description desc 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/integration/mysql/star_wars_introspection_test.rb: -------------------------------------------------------------------------------- 1 | require 'integration/config' 2 | 3 | class Integration_MySQL_StarWarsIntrospectionTest < GraphQL::IntegrationTestCase 4 | load_schema 'mysql' 5 | 6 | SCHEMA = ::StartWarsMySQLSchema 7 | 8 | def test_auto_introspection 9 | assert(SCHEMA.introspection?) 10 | assert(SCHEMA.has_field?(:query, :__schema)) 11 | assert(SCHEMA.has_field?(:query, :__type)) 12 | end 13 | 14 | # Test this spec with all available scalars 15 | def remove_keys_form_type_map 16 | end 17 | 18 | # There are some issues with the end sorting, so compare the string result 19 | # with sorted characters, which will produce the exact match 20 | def test_gql_introspection 21 | # File.write('test/assets/mysql.gql', SCHEMA.to_gql) 22 | result = gql_file('mysql').split('').sort.join.squish 23 | assert_equal(result, SCHEMA.to_gql.split('').sort.join.squish) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/integration/sqlite/star_wars_introspection_test.rb: -------------------------------------------------------------------------------- 1 | require 'integration/config' 2 | 3 | class Integration_SQLite_StarWarsIntrospectionTest < GraphQL::IntegrationTestCase 4 | load_schema 'sqlite' 5 | 6 | SCHEMA = ::StartWarsSqliteSchema 7 | 8 | def test_auto_introspection 9 | assert(SCHEMA.introspection?) 10 | assert(SCHEMA.has_field?(:query, :__schema)) 11 | assert(SCHEMA.has_field?(:query, :__type)) 12 | end 13 | 14 | # Test this spec with all available scalars 15 | def remove_keys_form_type_map 16 | end 17 | 18 | # There are some issues with the end sorting, so compare the string result 19 | # with sorted characters, which will produce the exact match 20 | def test_gql_introspection 21 | # File.write('test/assets/sqlite.gql', SCHEMA.to_gql) 22 | result = gql_file('sqlite').split('').sort.join.squish 23 | assert_equal(result, SCHEMA.to_gql.split('').sort.join.squish) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/rails/graphql/request/strategy/sequenced_strategy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Request 6 | # = GraphQl Sequenced Strategy 7 | # 8 | # This is the default resolution strategy, where each operation is 9 | # performed in sequece, and they don't relate to each other in any way. 10 | class Strategy::SequencedStrategy < Strategy 11 | def self.can_resolve?(_) 12 | true 13 | end 14 | 15 | # Executes the strategy in the normal mode 16 | def resolve! 17 | response.with_stack('data') do 18 | for_each_operation do |op| 19 | collect_listeners { op.organize! } 20 | collect_data(op.mutation?) { op.prepare! } 21 | collect_response { op.resolve! } 22 | end 23 | end 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /docs/_sass/typography.scss: -------------------------------------------------------------------------------- 1 | // Typography 2 | h1, 3 | .text-alpha { 4 | @include fs-8; 5 | font-weight: 900; 6 | } 7 | 8 | h2, 9 | .text-beta { 10 | @include fs-6; 11 | } 12 | 13 | h3, 14 | .text-gamma { 15 | @include fs-5; 16 | } 17 | 18 | h4, 19 | .text-delta { 20 | @include fs-2; 21 | font-weight: 400; 22 | text-transform: uppercase; 23 | letter-spacing: 0.1em; 24 | } 25 | 26 | h4 code { 27 | text-transform: none; 28 | } 29 | 30 | h5, 31 | .text-epsilon { 32 | @include fs-3; 33 | color: $grey-dk-200; 34 | } 35 | 36 | h6, 37 | .text-zeta { 38 | @include fs-2; 39 | color: $grey-dk-200; 40 | } 41 | 42 | .text-small { 43 | @include fs-2; 44 | } 45 | 46 | .text-mono { 47 | font-family: $mono-font-family !important; 48 | } 49 | 50 | .text-left { 51 | text-align: left !important; 52 | } 53 | 54 | .text-center { 55 | text-align: center !important; 56 | } 57 | 58 | .text-right { 59 | text-align: right !important; 60 | } 61 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/date_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_DateScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::DateScalar 5 | 6 | def test_valid_input_ask 7 | assert(DESCRIBED_CLASS.valid_input?('2020-02-02')) 8 | 9 | refute(DESCRIBED_CLASS.valid_input?(1)) 10 | refute(DESCRIBED_CLASS.valid_input?('abc')) 11 | refute(DESCRIBED_CLASS.valid_input?(nil)) 12 | end 13 | 14 | def test_valid_output_ask 15 | assert(DESCRIBED_CLASS.valid_output?('2020-02-02')) 16 | 17 | refute(DESCRIBED_CLASS.valid_output?('abc')) 18 | refute(DESCRIBED_CLASS.valid_output?(nil)) 19 | refute(DESCRIBED_CLASS.valid_output?(1)) 20 | end 21 | 22 | def test_as_json 23 | assert_equal('2020-02-02', DESCRIBED_CLASS.as_json('2020-02-02')) 24 | end 25 | 26 | def test_deserialize 27 | assert_kind_of(Date, DESCRIBED_CLASS.deserialize('2020-02-02')) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/float_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # The Float scalar type represents signed double-precision fractional 7 | # values as specified by 8 | # {IEEE 754}[http://en.wikipedia.org/wiki/IEEE_floating_point]. 9 | # 10 | # See http://spec.graphql.org/June2018/#sec-Float 11 | class Scalar::FloatScalar < Scalar 12 | self.spec_object = true 13 | 14 | desc 'The Float scalar type represents signed double-precision fractional values.' 15 | 16 | class << self 17 | def valid_input?(value) 18 | valid_token?(value) || value.is_a?(Float) 19 | end 20 | 21 | def valid_output?(value) 22 | value.respond_to?(:to_f) 23 | end 24 | 25 | def as_json(value) 26 | value.to_f 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/date_time_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # DateTime uses a ISO 8601 string to exchange the value. 7 | class Scalar::DateTimeScalar < Scalar 8 | aliases :datetime 9 | 10 | desc 'The DateTime scalar type represents a ISO 8601 string value.' 11 | 12 | use :specified_by, url: 'https://en.wikipedia.org/wiki/ISO_8601' 13 | 14 | class << self 15 | def valid_input?(value) 16 | super && !!(Time.iso8601(value) rescue false) 17 | end 18 | 19 | def valid_output?(value) 20 | value.respond_to?(:to_time) && !!value.to_time 21 | end 22 | 23 | def as_json(value) 24 | value.to_time.iso8601 25 | end 26 | 27 | def deserialize(value) 28 | Time.iso8601(value) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/date_time_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_DateTimeScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::DateTimeScalar 5 | 6 | def test_valid_input_ask 7 | assert(DESCRIBED_CLASS.valid_input?(Time.now.iso8601)) 8 | 9 | refute(DESCRIBED_CLASS.valid_input?(1)) 10 | refute(DESCRIBED_CLASS.valid_input?('abc')) 11 | refute(DESCRIBED_CLASS.valid_input?(nil)) 12 | end 13 | 14 | def test_valid_output_ask 15 | assert(DESCRIBED_CLASS.valid_output?(Time.now)) 16 | 17 | refute(DESCRIBED_CLASS.valid_output?('abc')) 18 | refute(DESCRIBED_CLASS.valid_output?(1)) 19 | refute(DESCRIBED_CLASS.valid_output?(nil)) 20 | end 21 | 22 | def test_as_json 23 | assert_equal(Time.strptime('2020-02-02', '%Y-%m-%d'), DESCRIBED_CLASS.as_json('2020-02-02')) 24 | end 25 | 26 | def test_deserialize 27 | assert_kind_of(Time, DESCRIBED_CLASS.deserialize('2020-02-02T00:00:00-03:00')) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/bigint_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # Bigint basically removes the limit of the value, but it serializes as 7 | # a string so it won't go against the spec 8 | class Scalar::BigintScalar < Scalar 9 | desc <<~DESC 10 | The Bigint scalar type represents a signed numeric non-fractional value. 11 | It can go beyond the Int 32-bit limit, but it's exchanged as a string. 12 | DESC 13 | 14 | class << self 15 | def valid_input?(value) 16 | super && value.match?(/\A[+-]?\d+\z/) 17 | end 18 | 19 | def valid_output?(value) 20 | value.respond_to?(:to_i) 21 | end 22 | 23 | def as_json(value) 24 | value.to_i.to_s 25 | end 26 | 27 | def deserialize(value) 28 | value.to_i 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /docs/_sass/overrides.scss: -------------------------------------------------------------------------------- 1 | // Override the main font of the website 2 | html { 3 | font-size: $root-font-size !important; 4 | } 5 | 6 | code { 7 | font-size: 0.875rem !important; 8 | } 9 | 10 | a:not([class]) { 11 | background-image: none; 12 | } 13 | 14 | .main-content { 15 | a:not([class]) { 16 | font-weight: bold; 17 | 18 | &:hover { 19 | text-decoration: underline; 20 | } 21 | } 22 | 23 | ol > li::before { 24 | top: 0; 25 | } 26 | 27 | h1, h2, h3, h4, h5, h6 { 28 | margin: 1.5rem 0; 29 | 30 | code { 31 | padding: 0.2em 0.5em; 32 | display: inline-block; 33 | vertical-align: text-top; 34 | } 35 | } 36 | 37 | h1, h2 { 38 | color: $red-200; 39 | } 40 | 41 | h4 { 42 | font-size: $font-size-4 !important; 43 | } 44 | 45 | h3 > code { 46 | vertical-align: text-bottom; 47 | } 48 | 49 | details.snippet > summary { 50 | margin-bottom: 1.5rem; 51 | } 52 | 53 | summary > h2 { 54 | margin: 0 !important; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ext/gql_parser.h: -------------------------------------------------------------------------------- 1 | #include "ruby.h" 2 | 3 | #define GQL_SAFE_PUSH_AND_NEXT(source, scanner, action) ({ \ 4 | GQL_SAFE_PUSH(source, action); \ 5 | gql_next_lexeme_no_comments(scanner); \ 6 | }) 7 | #define GQL_ASSIGN_TOKEN_AND_NEXT(source, scanner) (GQL_ASSIGN_VALUE_AND_NEXT(source, scanner, gql_scanner_to_token(scanner))) 8 | #define GQL_ASSIGN_VALUE_AND_NEXT(source, scanner, value) ({ \ 9 | source = value; \ 10 | gql_next_lexeme_no_comments(scanner); \ 11 | }) 12 | #define GQL_BUILD_PARSE_OUTER_TOKEN(type, size, pieces, scanner, mem) ({ \ 13 | gql_token_start_from_mem(GQL_BUILD_PARSE_TOKEN(type, size, pieces, scanner), mem); \ 14 | }) 15 | #define GQL_BUILD_PARSE_TOKEN(type, size, pieces, scanner) ({ \ 16 | gql_set_token_type(gql_as_token(rb_ary_new4(size, pieces), scanner, 0), type); \ 17 | }) 18 | 19 | VALUE GQLParser; 20 | VALUE QLGParserToken; 21 | VALUE gql_eParserError; 22 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/date_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # Date uses a ISO 8601 string to exchange the value. 7 | class Scalar::DateScalar < Scalar 8 | desc 'The Date scalar type represents a ISO 8601 string value.' 9 | 10 | use :specified_by, url: 'https://en.wikipedia.org/wiki/ISO_8601' 11 | 12 | class << self 13 | def valid_input?(value) 14 | super && !!Date.iso8601(value) 15 | rescue Date::Error 16 | false 17 | end 18 | 19 | def valid_output?(value) 20 | value.respond_to?(:to_date) && !!value.to_date 21 | rescue Date::Error 22 | false 23 | end 24 | 25 | def as_json(value) 26 | value.to_date.iso8601 27 | end 28 | 29 | def deserialize(value) 30 | Date.iso8601(value) 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/binary_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # Binary basically allows binary data to be shared using Base64 strings, 7 | # ensuring the UTF-8 encoding and performing the necessary conversion. 8 | # 9 | # It also rely on ActiveModel so it can easily share the same Data object. 10 | class Scalar::BinaryScalar < Scalar 11 | aliases :file 12 | 13 | desc <<~DESC 14 | The Binary scalar type represents a Base64 string. 15 | Normally used to share files and uploads. 16 | DESC 17 | 18 | use :specified_by, url: 'https://www.rfc-editor.org/rfc/rfc3548' 19 | 20 | class << self 21 | def as_json(value) 22 | Base64.encode64(value.to_s).chomp 23 | end 24 | 25 | def deserialize(value) 26 | ActiveModel::Type::Binary::Data.new(Base64.decode64(value)) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/any_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_AnyScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::AnyScalar 5 | 6 | OBJECTS = { 7 | 1 => [1, '1'], 8 | 'a' => ['a', '"a"'], 9 | true => [true, 'true'], 10 | 4.2 => [4.2, '4.2'], 11 | [1, 'a'] => [[1, 'a'], '[1,"a"]'], 12 | { a: 1, b: 'c' } => [{ 'a' => 1, 'b' => 'c' }, '{"a":1,"b":"c"}'], 13 | } 14 | 15 | def test_valid_input_ask 16 | OBJECTS.each_value do |(val, _)| 17 | assert(DESCRIBED_CLASS.valid_input?(val)) 18 | end 19 | end 20 | 21 | def test_valid_output_ask 22 | OBJECTS.each_value do |(val, _)| 23 | assert(DESCRIBED_CLASS.valid_output?(val)) 24 | end 25 | end 26 | 27 | def test_to_json 28 | OBJECTS.each do |source, (_, val)| 29 | assert_equal(val, DESCRIBED_CLASS.to_json(source)) 30 | end 31 | end 32 | 33 | def test_as_json 34 | OBJECTS.each do |source, (val, _)| 35 | assert_equal(val, DESCRIBED_CLASS.as_json(source)) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/rails/graphql/request/strategy/multi_query_strategy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Request 6 | # = GraphQl Multi Query Strategy 7 | # 8 | # This is a resolution strategy to solve requests that only contain 9 | # queries, allowing the strategy to collect all the information for all 10 | # the queries in a single step before resolving it. 11 | class Strategy::MultiQueryStrategy < Strategy 12 | self.priority = 10 13 | 14 | def self.can_resolve?(request) 15 | request.operations.each_value.all? { |op| op.of_type?(:query) } 16 | end 17 | 18 | # Executes the strategy in the normal mode 19 | def resolve! 20 | response.with_stack('data') do 21 | for_each_operation { |op| collect_listeners { op.organize! } } 22 | for_each_operation { |op| collect_data { op.prepare! } } 23 | for_each_operation { |op| collect_response { op.resolve! } } 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/boolean_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # The Boolean scalar type represents +true+ or +false+. 7 | # 8 | # See http://spec.graphql.org/June2018/#sec-Boolean 9 | class Scalar::BooleanScalar < Scalar 10 | self.spec_object = true 11 | aliases :bool 12 | 13 | desc 'The Boolean scalar type represents true or false.' 14 | 15 | FALSE_VALUES = ::ActiveModel::Type::Boolean::FALSE_VALUES 16 | 17 | class << self 18 | def valid_input?(value) 19 | valid_token?(value) || value === true || value === false 20 | end 21 | 22 | def valid_output?(*) 23 | true # Pretty much anything can be turned into a boolean 24 | end 25 | 26 | def as_json(value) 27 | !(value.nil? || FALSE_VALUES.include?(value)) 28 | end 29 | 30 | def deserialize(value) 31 | as_json(value) 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /docs/assets/js/events.js: -------------------------------------------------------------------------------- 1 | (function(site, doc) { 2 | // Shortcut for adding event listeners 3 | site.addEvent = function(el, type, handler) { 4 | if (el.attachEvent) { 5 | el.attachEvent('on'+type, handler); 6 | } else { 7 | el.addEventListener(type, handler); 8 | } 9 | }; 10 | 11 | // Shortcut for removing event listeners 12 | site.removeEvent = function(el, type, handler) { 13 | if (el.detachEvent) { 14 | el.detachEvent('on'+type, handler); 15 | } else { 16 | el.removeEventListener(type, handler); 17 | } 18 | }; 19 | 20 | // Shortcut for when js is ready 21 | site.onReady = function(ready) { 22 | if (doc.readyState != 'loading') { 23 | // in case the document is already rendered 24 | ready(); 25 | } else if (doc.addEventListener) { 26 | // modern browsers 27 | doc.addEventListener('DOMContentLoaded', ready) 28 | } else { 29 | // IE <= 8 30 | doc.attachEvent('onreadystatechange', function() { 31 | (doc.readyState == 'complete') && ready(); 32 | }); 33 | } 34 | }; 35 | })(window.site = {}, document); 36 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/enum/directive_location_enum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # Bigint basically removes the limit of the value, but it serializes as 7 | # a string so it won't go against the spec 8 | class Enum::DirectiveLocationEnum < Enum 9 | self.spec_object = true 10 | 11 | rename! '__DirectiveLocation' 12 | 13 | desc 'The valid locations that a directive may be placed.' 14 | 15 | Directive::EXECUTION_LOCATIONS.each do |value| 16 | name = value.to_s.upcase 17 | desc = value.to_s.tr('_', ' ') 18 | add(name, desc: "Mark as a executable directive usable on #{desc} objects.") 19 | end 20 | 21 | Directive::DEFINITION_LOCATIONS.each do |value| 22 | name = value.to_s.upcase 23 | desc = value.to_s.tr('_', ' ') 24 | desc = "Mark as a type system directive usable on #{desc} definitions." 25 | add(name, desc: desc.sub('definition definitions.', 'definitions.')) 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/json_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # Handles an unstructured JSON data 7 | class Scalar::JsonScalar < Scalar 8 | rename! 'JSON' 9 | 10 | desc <<~DESC 11 | The JSON scalar type represents an unstructured JSON data 12 | with all its available keys and values. 13 | DESC 14 | 15 | use :specified_by, url: 'https://www.rfc-editor.org/rfc/rfc8259' 16 | 17 | class << self 18 | def valid_input?(value) 19 | valid_token?(value, :hash) || value.is_a?(::Hash) 20 | end 21 | 22 | def valid_output?(value) 23 | value.is_a?(::Hash) 24 | end 25 | 26 | def to_json(value) 27 | value.to_json 28 | end 29 | 30 | def as_json(value) 31 | value.as_json 32 | end 33 | 34 | def deserialize(value) 35 | value.is_a?(::GQLParser::Token) ? JSON.parse(value) : value 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 "VirtualShield LLC" 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/rails/graphql/directive/cached_directive.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # = GraphQL Cached Directive 6 | # 7 | # Indicates that the request has hard cached operations that need to be 8 | # collected 9 | class Directive::CachedDirective < Directive 10 | placed_on :query 11 | 12 | desc 'Indicates that there are hard cached operations.' 13 | 14 | argument :id, :ID, null: false, desc: <<~DESC 15 | The unique identifier of the cached operation. 16 | DESC 17 | 18 | on(:attach) do |source, request| 19 | source.data.selection = nil 20 | # TODO: Add the request name back 21 | # source.instance_variable_set(:@name, 'here') 22 | 23 | # TODO: Add the arguments and variables 24 | field = request.build(Request::Component::Field, source, nil, { name: 'a', alias: 'b' }) 25 | field.assing_to(ApplicationSchema[:query][:a]) 26 | field.check_authorization! 27 | 28 | source.instance_variable_set(:@selection, { 'b' => field }) 29 | # puts source.inspect 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/decimal_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # Similar to float, but implies extra precision, making sure that all the 7 | # decimal-point numbers are kept. As Bigint, it uses a string so it won't 8 | # go against the spec. 9 | class Scalar::DecimalScalar < Scalar 10 | desc <<~DESC 11 | The Decimal scalar type represents signed fractional values with extra precision. 12 | The values are exchange as string. 13 | DESC 14 | 15 | use :specified_by, url: 'https://en.wikipedia.org/wiki/IEEE_754-2008_revision' 16 | 17 | class << self 18 | def valid_input?(value) 19 | super && value.match?(/\A[+-]?\d+\.\d+\z/) 20 | end 21 | 22 | def valid_output?(value) 23 | value.respond_to?(:to_d) 24 | end 25 | 26 | def as_json(value) 27 | value.to_d.to_s 28 | end 29 | 30 | def deserialize(value) 31 | value.to_d 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/int_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # The Int scalar type represents a signed 32-bit numeric 7 | # non-fractional value. 8 | # 9 | # See http://spec.graphql.org/June2018/#sec-Int 10 | class Scalar::IntScalar < Scalar 11 | self.spec_object = true 12 | aliases :integer 13 | 14 | desc 'The Int scalar type represents a signed 32-bit numeric non-fractional value.' 15 | 16 | max_value = (1 << 31) 17 | RANGE = (-max_value...max_value).freeze 18 | 19 | class << self 20 | def valid_input?(value) 21 | (valid_token?(value) && RANGE.cover?(value.to_i)) || 22 | (value.is_a?(Integer) && RANGE.cover?(value)) 23 | end 24 | 25 | def valid_output?(value) 26 | value.respond_to?(:to_i) && RANGE.cover?(value.to_i) 27 | end 28 | 29 | def as_json(value) 30 | value = value.to_i 31 | value if RANGE.cover?(value) 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /docs/guides/advanced/abstract.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Abstract - Advanced - Guides 4 | description: All about abstract components 5 | --- 6 | 7 | # Abstract 8 | 9 | Most of the GraphQL components of this gem can be marked as `abstract`, which 10 | indicates that such components must be inherited first before being used. 11 | 12 | There are two main reasons why to mark a component as abstract: 13 | 14 | 1. They are not registered to the [Type Map](/guides/type-map); 15 | 1. They can't be instantiated directly during [events](/guides/events). 16 | 17 | This feature is recommended for those that wish to create other gems based on 18 | this one. 19 | 20 | {: .important } 21 | > **Important** 22 | > This feature is under review, which means it may behave differently 23 | > than described in some situations. 24 | 25 | ## How to Use It 26 | 27 | To mark a component as abstract, you can simply: 28 | 29 | ```ruby 30 | # app/graphql/objects/base_object.rb 31 | self.abstract = true 32 | ``` 33 | 34 | You use of this to add shared methods, set configurations like 35 | [namespaces](/guides/advanced/namespaces), and create intermediate steps 36 | for [sources](/guides/sources#hooks). 37 | -------------------------------------------------------------------------------- /docs/_sass/tables.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Tables 3 | // 4 | // stylelint-disable max-nesting-depth, selector-no-type, selector-max-type 5 | 6 | .table-wrapper { 7 | display: block; 8 | width: 100%; 9 | max-width: 100%; 10 | margin-bottom: $sp-5; 11 | overflow-x: auto; 12 | border-radius: $border-radius; 13 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 3px 10px rgba(0, 0, 0, 0.08); 14 | } 15 | 16 | table { 17 | display: table; 18 | min-width: 100%; 19 | border-collapse: separate; 20 | } 21 | 22 | th, 23 | td { 24 | @include fs-3; 25 | min-width: 120px; 26 | padding-top: $sp-2; 27 | padding-right: $sp-3; 28 | padding-bottom: $sp-2; 29 | padding-left: $sp-3; 30 | background-color: $table-background-color; 31 | border-bottom: $border rgba($border-color, 0.5); 32 | border-left: $border $border-color; 33 | 34 | &:first-of-type { 35 | border-left: 0; 36 | } 37 | } 38 | 39 | tbody { 40 | tr { 41 | &:last-of-type { 42 | th, 43 | td { 44 | border-bottom: 0; 45 | } 46 | 47 | td { 48 | padding-bottom: $sp-3; 49 | } 50 | } 51 | } 52 | } 53 | 54 | thead { 55 | th { 56 | border-bottom: $border $border-color; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/rails/graphql/railties/controller_runtime.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/module/attr_internal' 4 | 5 | module Rails 6 | module GraphQL 7 | # = GraphQL Controller Runtime 8 | # 9 | # Tool that calculates the runtime of a GraphQL operation. This works 10 | # similar to how Rails ActiveRecord calculate its execution time while 11 | # performing a request 12 | module ControllerRuntime 13 | extend ActiveSupport::Concern 14 | 15 | module ClassMethods 16 | def log_process_action(payload) 17 | messages, gql_runtime = super, payload[:gql_runtime] 18 | messages << format(+'GraphQL: %.1fms', gql_runtime.to_f) if gql_runtime 19 | messages 20 | end 21 | end 22 | 23 | private 24 | 25 | attr_internal :gql_runtime 26 | 27 | def process_action(*) 28 | LogSubscriber.runtime = 0 29 | super 30 | end 31 | 32 | def append_info_to_payload(payload) 33 | super 34 | 35 | payload[:gql_runtime] = LogSubscriber.runtime \ 36 | if (LogSubscriber.runtime || 0).positive? 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/rails/graphql/request/steps/resolvable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Request 6 | # Helper methods for the resolve step of a request 7 | module Resolvable 8 | # Resolve the object 9 | def resolve! 10 | resolve 11 | rescue => error 12 | report_exception(error) 13 | end 14 | 15 | protected 16 | 17 | # Normal mode of the resolve step 18 | # TODO: Field a way to cache the resolved result, mostly for 19 | # performance improvement in recursion 20 | def resolve 21 | return if skipped? 22 | invalid? ? try(:resolve_invalid) : resolve_then 23 | rescue 24 | try(:resolve_invalid) 25 | raise 26 | end 27 | 28 | # The actual process that resolve the object 29 | def resolve_then(after_block = nil, &block) 30 | return if unresolvable? 31 | 32 | stacked do 33 | block.call if block.present? 34 | after_block.call if after_block.present? 35 | trigger_event(:finalize) 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /docs/_includes/3-steps.html: -------------------------------------------------------------------------------- 1 |

3 Simple Steps

2 |
3 |
4 |

Install

5 |

Totally plug-in-play

6 | {% highlight bash %} 7 | # Add the gem to your Gemfile 8 | $ bundle add rails-graphql 9 | # Then run the Rails generator 10 | $ rails g graphql:install 11 | {% endhighlight %} 12 |
13 |
14 |

Define

15 |

Designed for simple and complex Schemas

16 | {% highlight ruby %} 17 | # app/graphql/app_schema.rb 18 | class GraphQL::AppSchema < GraphQL::Schema 19 | field(:welcome).resolve { 'Hello World!' } 20 | end 21 | {% endhighlight %} 22 |
23 |
24 |

Run

25 |

26 | Follows 27 | 28 | Rails core principles 29 | 30 |

31 | {% highlight bash %} 32 | $ curl -d '{"query":"{ welcome }"}' \ 33 | -H "Content-Type: application/json" \ 34 | -X POST http://localhost:3000/graphql 35 | # {"data":{"welcome":"Hello World!"}} 36 | {% endhighlight %} 37 |
38 |
39 | -------------------------------------------------------------------------------- /docs/_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% if jekyll.environment == "production" %} 12 | {% include tracking.html %} 13 | {% endif %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% if page.description %} 23 | 24 | {% endif %} 25 | 26 | {% seo %} 27 | 28 | -------------------------------------------------------------------------------- /lib/rails/graphql/request/steps/preparable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Request 6 | # Helper methods for the prepare step of a request 7 | module Preparable 8 | # Prepare the object 9 | def prepare! 10 | prepare 11 | rescue => error 12 | report_exception(error) 13 | end 14 | 15 | # Marks that the field has prepared data that came outside from the 16 | # request 17 | def prepared_data! 18 | @prepared_data = true 19 | end 20 | 21 | # Checks if the field has prepared data 22 | def prepared_data? 23 | defined?(@prepared_data) && @prepared_data 24 | end 25 | 26 | protected 27 | 28 | # Normal mode of the prepare step 29 | def prepare 30 | prepare_then { prepare_fields } 31 | end 32 | 33 | # The actual process that prepare the object 34 | def prepare_then(after_block = nil, &block) 35 | return if unresolvable? 36 | 37 | stacked do 38 | block.call if block.present? 39 | after_block.call if after_block.present? 40 | trigger_event(:prepared) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/rails/graphql/field/authorized_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # This provides ways for fields to be authorized, giving a logical level for 6 | # enabling or disabling access to a field. It has a similar structure to 7 | # events, but has a different hierarchy of resolution 8 | module Field::AuthorizedField 9 | module Proxied # :nodoc: all 10 | def authorizer 11 | super || field.authorizer 12 | end 13 | 14 | def authorizable? 15 | super || field.authorizable? 16 | end 17 | end 18 | 19 | # Add either settings for authorization or a block to be executed. It 20 | # returns +self+ for chain purposes 21 | def authorize(*args, **xargs, &block) 22 | @authorizer = [args, xargs, block] 23 | self 24 | end 25 | 26 | # Return the settings for the authorize process 27 | def authorizer 28 | @authorizer if authorizable? 29 | end 30 | 31 | # Checks if the field should go through an authorization process 32 | def authorizable? 33 | defined?(@authorizer) 34 | end 35 | 36 | protected 37 | 38 | def proxied 39 | super if defined? super 40 | extend Field::AuthorizedField::Proxied 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/integration/customization_test.rb: -------------------------------------------------------------------------------- 1 | require 'integration/config' 2 | 3 | class Integration_CustomizationTest < GraphQL::IntegrationTestCase 4 | class SCHEMA < GraphQL::Schema 5 | namespace :customization 6 | 7 | configure do |config| 8 | config.schema_type_names = { 9 | query: 'A', 10 | mutation: 'B', 11 | subscription: 'C', 12 | } 13 | end 14 | 15 | query_fields { field(:one, :string) } 16 | mutation_fields { field(:two, :string) } 17 | subscription_fields { field(:three, :string) } 18 | end 19 | 20 | def test_type_name_for 21 | assert_equal('A', SCHEMA.type_name_for(:query)) 22 | assert_equal('B', SCHEMA.type_name_for(:mutation)) 23 | assert_equal('C', SCHEMA.type_name_for(:subscription)) 24 | end 25 | 26 | def test_type_map 27 | assert_equal(SCHEMA.query_type, SCHEMA.find_type('A')) 28 | assert_equal(SCHEMA.mutation_type, SCHEMA.find_type('B')) 29 | assert_equal(SCHEMA.subscription_type, SCHEMA.find_type('C')) 30 | end 31 | 32 | def test_cache_prefix 33 | assert_equal('graphql/customization/', SCHEMA.config.cache_prefix) 34 | SCHEMA.config.cache_prefix = 'banana' 35 | assert_equal('banana', SCHEMA.config.cache_prefix) 36 | SCHEMA.config.cache_prefix = nil 37 | assert_equal('graphql/customization/', SCHEMA.config.cache_prefix) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/object/directive_object.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # The introspection object for directives 7 | class Object::DirectiveObject < Object 8 | self.assigned_to = 'Rails::GraphQL::Directive' 9 | self.spec_object = true 10 | 11 | rename! '__Directive' 12 | 13 | desc <<~DESC 14 | Directives provide a way to describe alternate runtime execution 15 | and type validation behavior in a GraphQL document. 16 | 17 | In some cases, you need to provide options to alter GraphQL's execution 18 | behavior in ways field arguments will not suffice, such as conditionally 19 | including or skipping a field. Directives provide this by describing 20 | additional information to the executor. 21 | DESC 22 | 23 | field :name, :string, null: false, method_name: :gql_name 24 | field :description, :string 25 | field :locations, '__DirectiveLocation', full: true 26 | field :args, '__InputValue', full: true 27 | field :is_repeatable, :boolean, null: false, method_name: :repeatable? 28 | 29 | def args 30 | all_arguments.each_value 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/string_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # The String scalar type represents textual data, represented as UTF-8 7 | # character sequences. 8 | # 9 | # See http://spec.graphql.org/June2018/#sec-String 10 | class Scalar::StringScalar < Scalar 11 | self.spec_object = true 12 | 13 | desc <<~DESC 14 | The String scalar type represents textual data, represented as UTF-8 character 15 | sequences. 16 | DESC 17 | 18 | class << self 19 | def valid_input?(value) 20 | super || valid_token?(value, :heredoc) 21 | end 22 | 23 | def as_json(value) 24 | value = value.to_s unless value.is_a?(String) 25 | value = value.encode(Encoding::UTF_8) unless value.encoding.eql?(Encoding::UTF_8) 26 | value 27 | end 28 | 29 | def deserialize(value) 30 | if valid_token?(value, :string) 31 | value[1..-2] # Remove the quotes 32 | elsif valid_token?(value, :heredoc) 33 | value[3..-4].strip_heredoc # Remove the quotes and fix indentation 34 | else 35 | value 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/id_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # The ID scalar type represents a unique identifier, often used to 7 | # refetch an object or as the key for a cache. The ID type is serialized 8 | # in the same way as a +StringScalar+. 9 | # 10 | # See http://spec.graphql.org/June2018/#sec-ID 11 | class Scalar::IdScalar < Scalar 12 | self.spec_object = true 13 | 14 | rename! 'ID' 15 | 16 | desc <<~DESC 17 | The ID scalar type represents a unique identifier and it is serialized in the same 18 | way as a String but it accepts both numeric and string based values as input. 19 | DESC 20 | 21 | class << self 22 | def valid_input?(value) 23 | valid_token?(value, :string) || valid_token?(value, :int) || 24 | value.is_a?(String) || value.is_a?(Integer) 25 | end 26 | 27 | def as_json(value) 28 | value = value.to_s unless value.is_a?(String) 29 | value = value.encode(Encoding::UTF_8) unless value.encoding.eql?(Encoding::UTF_8) 30 | value 31 | end 32 | 33 | def deserialize(value) 34 | valid_token?(value, :string) ? value[1..-2] : value 35 | end 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/scalar/time_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # Uses as a float extension in order to transmit times (hours, minutes, 7 | # and seconds) as a numeric representation of seconds and milliseconds. 8 | class Scalar::TimeScalar < Scalar::FloatScalar 9 | EPOCH = Time.utc(2000, 1, 1) 10 | 11 | desc <<~MSG 12 | The Time scalar type that represents a distance in time using hours, 13 | minutes, seconds, and milliseconds. 14 | MSG 15 | 16 | use :specified_by, url: 'https://en.wikipedia.org/wiki/ISO_8601' 17 | 18 | # A +base_object+ helps to identify what methods are actually available 19 | # to work as resolvers 20 | class_attribute :precision, instance_accessor: false, default: 6 21 | 22 | class << self 23 | def valid_input?(value) 24 | value.match?(/\d+:\d\d(:\d\d(\.\d+)?)?/) 25 | end 26 | 27 | def valid_output?(value) 28 | value.respond_to?(:to_time) 29 | end 30 | 31 | def as_json(value) 32 | value.to_time.strftime(format('%%T.%%%dN', precision)) 33 | end 34 | 35 | def deserialize(value) 36 | (+"#{EPOCH.to_date.iso8601} #{value} UTC").to_time 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/time_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_TimeScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::TimeScalar 5 | 6 | def test_valid_input_ask 7 | assert(DESCRIBED_CLASS.valid_input?('1:12')) 8 | assert(DESCRIBED_CLASS.valid_input?('12:12')) 9 | assert(DESCRIBED_CLASS.valid_input?('123:12')) 10 | assert(DESCRIBED_CLASS.valid_input?('12:12:12')) 11 | assert(DESCRIBED_CLASS.valid_input?('12:12:12.123')) 12 | 13 | refute(DESCRIBED_CLASS.valid_input?('foo')) 14 | end 15 | 16 | def test_valid_output_ask 17 | assert(DESCRIBED_CLASS.valid_output?('12:12'.to_time)) 18 | assert(DESCRIBED_CLASS.valid_output?(DateTime.current)) 19 | assert(DESCRIBED_CLASS.valid_output?(Time.current)) 20 | 21 | refute(DESCRIBED_CLASS.valid_output?('foo'.to_time)) 22 | end 23 | 24 | def test_as_json 25 | assert_equal('12:12:00.000000', DESCRIBED_CLASS.as_json('12:12'.to_time)) 26 | assert_equal('12:12:12.000000', DESCRIBED_CLASS.as_json('12:12:12'.to_time)) 27 | assert_equal('12:12:12.120000', DESCRIBED_CLASS.as_json('12:12:12.12'.to_time)) 28 | end 29 | 30 | def test_deserialize 31 | Time.use_zone('UTC') do 32 | assert_kind_of(Time, DESCRIBED_CLASS.deserialize('01:00')) 33 | assert_equal('2000-01-01 01:00:00 -0000'.to_time, DESCRIBED_CLASS.deserialize('01:00')) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/object/schema_object.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # The introspection object for a schema object 7 | class Object::SchemaObject < Object 8 | self.assigned_to = 'Rails::GraphQL::Schema' 9 | self.spec_object = true 10 | 11 | rename! '__Schema' 12 | 13 | desc <<~DESC 14 | A GraphQL service's collective type system capabilities are referred 15 | to as that service's "schema". A schema is defined in terms of the 16 | types and directives it supports as well as the root operation types 17 | for each kind of operation: query, mutation, and subscription; this 18 | determines the place in the type system where those operations begin. 19 | DESC 20 | 21 | field :types, '__Type', full: true, method_name: :read_types 22 | field :query_type, '__Type', null: false 23 | field :mutation_type, '__Type' 24 | field :subscription_type, '__Type' 25 | field :directives, '__Directive', full: true, method_name: :read_directives 26 | 27 | def read_types 28 | event.schema.types(base_class: :Type).force 29 | end 30 | 31 | def read_directives 32 | event.schema.types(base_class: :Directive).force 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /docs/_sass/utilities/_typography.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Utility classes for typography 3 | // 4 | 5 | // stylelint-disable primer/selector-no-utility, primer/no-override 6 | 7 | .fs-1 { 8 | @include fs-1; 9 | } 10 | 11 | .fs-2 { 12 | @include fs-2; 13 | } 14 | 15 | .fs-3 { 16 | @include fs-3; 17 | } 18 | 19 | .fs-4 { 20 | @include fs-4; 21 | } 22 | 23 | .fs-5 { 24 | @include fs-5; 25 | } 26 | 27 | .fs-6 { 28 | @include fs-6; 29 | } 30 | 31 | .fs-7 { 32 | @include fs-7; 33 | } 34 | 35 | .fs-8 { 36 | @include fs-8; 37 | } 38 | 39 | .fs-9 { 40 | @include fs-9; 41 | } 42 | 43 | .fs-10 { 44 | @include fs-10; 45 | } 46 | 47 | .fw-100 { 48 | font-weight: 100 !important; 49 | } 50 | 51 | .fw-300 { 52 | font-weight: 300 !important; 53 | } 54 | 55 | .fw-400 { 56 | font-weight: 400 !important; 57 | } 58 | 59 | .fw-700 { 60 | font-weight: 700 !important; 61 | } 62 | 63 | .fw-900 { 64 | font-weight: 900 !important; 65 | } 66 | 67 | .lh-0 { 68 | line-height: 0 !important; 69 | } 70 | 71 | .lh-default { 72 | line-height: $body-line-height; 73 | } 74 | 75 | .lh-tight { 76 | line-height: $body-heading-line-height; 77 | } 78 | 79 | .ls-5 { 80 | letter-spacing: 0.05em !important; 81 | } 82 | 83 | .ls-10 { 84 | letter-spacing: 0.1em !important; 85 | } 86 | 87 | .ls-0 { 88 | letter-spacing: 0 !important; 89 | } 90 | 91 | .text-uppercase { 92 | text-transform: uppercase !important; 93 | } 94 | 95 | // stylelint-enable primer/selector-no-utility 96 | -------------------------------------------------------------------------------- /docs/_includes/breadcrumb.html: -------------------------------------------------------------------------------- 1 | {% unless page.url == "/" %} 2 | {% if page.parent %} 3 | {%- for node in pages_list -%} 4 | {%- if node.parent == nil -%} 5 | {%- if page.parent == node.title or page.grand_parent == node.title -%} 6 | {%- assign first_level_url = node.url | absolute_url -%} 7 | {%- endif -%} 8 | {%- if node.has_children -%} 9 | {%- assign children_list = pages_list | where: "parent", node.title -%} 10 | {%- for child in children_list -%} 11 | {%- if page.url == child.url or page.parent == child.title -%} 12 | {%- assign second_level_url = child.url | absolute_url -%} 13 | {%- endif -%} 14 | {%- endfor -%} 15 | {%- endif -%} 16 | {%- endif -%} 17 | {%- endfor -%} 18 | 29 | {% endif %} 30 | {% endunless %} 31 | -------------------------------------------------------------------------------- /docs/assets/js/code.js: -------------------------------------------------------------------------------- 1 | (function(win, site) { 2 | site.onReady(function() { 3 | var codeBlocks = document.querySelectorAll('div.highlighter-rouge, *:not(div.highlighter-rouge) > figure.highlight'); 4 | 5 | var svgCopied = ''; 6 | var svgCopy = ''; 7 | 8 | codeBlocks.forEach(function(codeBlock) { 9 | var copyButton = document.createElement('button'); 10 | var timeout = null; 11 | 12 | copyButton.type = 'button'; 13 | copyButton.title = 'Copy'; 14 | copyButton.ariaLabel = 'Copy code to clipboard'; 15 | copyButton.innerHTML = svgCopy; 16 | codeBlock.append(copyButton); 17 | 18 | site.addEvent(copyButton, 'click', function() { 19 | if(win.navigator.clipboard && timeout === null) { 20 | var code = codeBlock.querySelector('pre:not(.lineno)').innerText; 21 | win.navigator.clipboard.writeText(code); 22 | 23 | copyButton.title = 'Copied'; 24 | copyButton.innerHTML = svgCopied; 25 | 26 | var timeoutSetting = 4000; 27 | timeout = setTimeout(function() { 28 | copyButton.title = 'Copy'; 29 | copyButton.innerHTML = svgCopy; 30 | timeout = null; 31 | }, timeoutSetting); 32 | } 33 | }); 34 | }); 35 | }); 36 | })(window, window.site); 37 | -------------------------------------------------------------------------------- /lib/rails/graphql/helpers/with_name.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/module/anonymous' 4 | 5 | module Rails 6 | module GraphQL 7 | module Helpers 8 | # Helper module responsible for name stuff 9 | module WithName 10 | NAME_EXP = /GraphQL::(?:Type::\w+::|Directive::)?([:\w]+)\z/.freeze 11 | 12 | # Here we define a couple of attributes used by registration 13 | def self.extended(other) 14 | # TODO: Move to registerable 15 | # An abstract type won't appear in the introspection and will not be 16 | # instantiated by requests 17 | other.class_attribute :abstract, instance_accessor: false, default: false 18 | end 19 | 20 | # Return the name of the object as a GraphQL name 21 | def gql_name 22 | @gql_name ||= begin 23 | result = name.match(NAME_EXP).try(:[], 1) 24 | result.tr(':', '').chomp(base_type.name.demodulize) unless result.nil? 25 | end unless anonymous? 26 | end 27 | 28 | alias graphql_name gql_name 29 | 30 | # Return the name of the object as a symbol 31 | def to_sym 32 | @gql_key ||= gql_name&.underscore&.to_sym 33 | end 34 | 35 | protected 36 | 37 | # Change the gql name of the object 38 | def rename!(name) 39 | @gql_name = name.to_s 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/boolean_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_BooleanScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::BooleanScalar 5 | 6 | def test_valid_input_ask 7 | assert(DESCRIBED_CLASS.valid_input?(true)) 8 | assert(DESCRIBED_CLASS.valid_input?(false)) 9 | 10 | refute(DESCRIBED_CLASS.valid_input?(nil)) 11 | refute(DESCRIBED_CLASS.valid_input?(1)) 12 | refute(DESCRIBED_CLASS.valid_input?('true')) 13 | end 14 | 15 | def test_valid_output_ask 16 | assert(DESCRIBED_CLASS.valid_output?(true)) 17 | assert(DESCRIBED_CLASS.valid_output?(false)) 18 | assert(DESCRIBED_CLASS.valid_output?(nil)) 19 | end 20 | 21 | def test_as_json 22 | assert(DESCRIBED_CLASS.as_json(true)) 23 | assert(DESCRIBED_CLASS.as_json([1])) 24 | assert(DESCRIBED_CLASS.as_json([])) 25 | assert(DESCRIBED_CLASS.as_json('abc')) 26 | assert(DESCRIBED_CLASS.as_json('')) 27 | 28 | refute(DESCRIBED_CLASS.as_json(nil)) 29 | refute(DESCRIBED_CLASS.as_json(0)) 30 | refute(DESCRIBED_CLASS.as_json('f')) 31 | refute(DESCRIBED_CLASS.as_json('false')) 32 | end 33 | 34 | def test_deserialize 35 | assert(DESCRIBED_CLASS.deserialize(1)) 36 | assert(DESCRIBED_CLASS.deserialize('abc')) 37 | assert(DESCRIBED_CLASS.deserialize(true)) 38 | 39 | refute(DESCRIBED_CLASS.deserialize(0)) 40 | refute(DESCRIBED_CLASS.deserialize(nil)) 41 | refute(DESCRIBED_CLASS.deserialize(false)) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/rails/graphql/railties/app/views/_cable.js.erb: -------------------------------------------------------------------------------- 1 | var queue = []; 2 | var current = null; 3 | var identifier = JSON.stringify({ channel: '<%= channel %>' }); 4 | var socket = new WebSocket("ws://" + window.location.hostname + "/<%= url %>"); 5 | 6 | // TOOD: This is a temporary implementation 7 | socket.onopen = function(event) { 8 | const msg = { command: 'subscribe', identifier: identifier }; 9 | socket.send(JSON.stringify(msg)); 10 | }; 11 | 12 | socket.onmessage = function(event) { 13 | const msg = JSON.parse(event.data); 14 | if (msg.type === "ping") { 15 | return; 16 | } 17 | 18 | if (msg.type === "confirm_subscription") { 19 | execute_next(); 20 | return; 21 | } 22 | 23 | if (msg.message && current) { 24 | current.resolve(msg.message.result); 25 | current = null; 26 | execute_next(); 27 | } else { 28 | console.dir(msg); 29 | } 30 | }; 31 | 32 | function execute_next() { 33 | if (socket.readyState != '1' || queue.length === 0 || current) { 34 | return; 35 | } 36 | 37 | current = queue.shift(); 38 | socket.send(JSON.stringify({ 39 | command: 'message', 40 | identifier: identifier, 41 | data: JSON.stringify({ action: 'execute', ...current.data }), 42 | })); 43 | } 44 | 45 | function graphQLFetcher(graphQLParams) { 46 | var resolve; 47 | var promise = new Promise((success) => { 48 | resolve = success; 49 | }); 50 | 51 | var item = { data: graphQLParams, promise, resolve }; 52 | 53 | queue.push(item); 54 | execute_next(); 55 | return promise; 56 | }; 57 | -------------------------------------------------------------------------------- /docs/guides/subscriptions/memory-store.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Memory Store - Stores - Subscriptions - Guides 4 | description: Stores all the subscriptions in memory 5 | --- 6 | 7 | # Memory Store 8 | 9 | The Memory Store will keep all the active subscriptions in the memory of your Rails instance. 10 | It still works with multiple instances because it just forces the instance that added the 11 | subscription to be responsible for delivering updates. 12 | 13 | The subscription objects are indexed in two ways: by their `sid` and by the combination of 14 | `field`, `scope`, and `arguments`. The second helps the memory store to fetch objects for an update easily. 15 | 16 | ## Fingerprint 17 | 18 | This store uses 19 | `#hash` 20 | to keep the memory fingerprint as small as possible. This is fully compatible with 21 | all features provided by this gem and Rails as well, especially ActiveRecord. 22 | 23 | You can take advantage of that when triggering updates. For example, if you don't actually 24 | need to load a record to trigger an update where this record is part of the scope, you can 25 | use the following approach: 26 | 27 | ```ruby 28 | field = GraphQL::AppSchema[:subscription][:user] 29 | field.trigger(scope: { User => 1 }) 30 | # This is the same as 31 | field.trigger(scope: User.find(1)) 32 | # OR 33 | field.trigger(scope: User.find(1).hash) 34 | # They all use the same approach towards `#hash` 35 | User.hash ^ 1.hash 36 | ``` 37 | -------------------------------------------------------------------------------- /lib/rails/graphql/helpers/with_namespace.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | module Helpers 6 | # Helper module that allows other objects to hold namespaces. It can 7 | # either work as an extension of the superclass using +add_namespace+ or 8 | # it can be reset then set using +namespace+. 9 | module WithNamespace 10 | # Returns the list of defined namespaces 11 | def namespaces 12 | return @namespaces if defined?(@namespaces) 13 | superclass.try(:namespaces) || begin 14 | value = GraphQL.type_map.associated_namespace_of(self) 15 | @namespaces = value unless value.nil? 16 | end 17 | end 18 | 19 | # Set or overwrite the list of namespaces 20 | def set_namespace(*list) 21 | @namespaces = normalize_namespaces(list).to_set 22 | end 23 | 24 | alias set_namespaces set_namespace 25 | 26 | # Add more namespaces to the list already defined. If the super class 27 | # has already defined namespaces, the extend that list. 28 | def namespace(*list) 29 | @namespaces = (superclass.try(:namespaces)&.dup || Set.new) \ 30 | unless defined?(@namespaces) 31 | 32 | @namespaces.merge(normalize_namespaces(list)) 33 | end 34 | 35 | private 36 | 37 | def normalize_namespaces(list) 38 | list.map { |item| item.to_s.underscore.to_sym } 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/graphql/type/scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_ScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = unmapped_class(Rails::GraphQL::Type::Scalar) 5 | 6 | def test_valid_input_ask 7 | assert(DESCRIBED_CLASS.valid_input?("abc")) 8 | refute(DESCRIBED_CLASS.valid_input?(1)) 9 | refute(DESCRIBED_CLASS.valid_input?(nil)) 10 | end 11 | 12 | def test_valid_output_ask 13 | assert(DESCRIBED_CLASS.valid_output?(1)) 14 | assert(DESCRIBED_CLASS.valid_output?(nil)) 15 | assert(DESCRIBED_CLASS.valid_output?("abc")) 16 | end 17 | 18 | def test_to_json 19 | assert_equal("\"1\"", DESCRIBED_CLASS.to_json(1)) 20 | assert_equal("\"abc\"", DESCRIBED_CLASS.to_json('abc')) 21 | assert_equal("\"\"", DESCRIBED_CLASS.to_json(nil)) 22 | end 23 | 24 | def test_as_json 25 | assert_equal('abc', DESCRIBED_CLASS.as_json('abc')) 26 | assert_equal('1', DESCRIBED_CLASS.as_json(1)) 27 | assert_equal('', DESCRIBED_CLASS.as_json(nil)) 28 | end 29 | 30 | def test_deserialize 31 | assert_nil(DESCRIBED_CLASS.deserialize(nil)) 32 | assert_equal('abc', DESCRIBED_CLASS.deserialize('abc')) 33 | assert_equal(1, DESCRIBED_CLASS.deserialize(1)) 34 | end 35 | 36 | def test_inspect 37 | DESCRIBED_CLASS.stub(:name, 'GraphQL::TestScalar') do 38 | assert_equal('#', DESCRIBED_CLASS.inspect) 39 | end 40 | 41 | DESCRIBED_CLASS.stub(:name, 'GraphQL::Test') do 42 | assert_equal('#', DESCRIBED_CLASS.inspect) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/rails/graphql/alternative/field_set.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | module Alternative 6 | # = GraphQL Alternative Field Set 7 | # 8 | # A simple way to store fields that share some logic with each other 9 | class FieldSet 10 | extend Helpers::WithNamespace 11 | extend Helpers::WithFields 12 | 13 | include Helpers::Instantiable 14 | 15 | self.field_type = GraphQL::Field::OutputField 16 | self.valid_field_types = Type::Object.valid_field_types 17 | 18 | def self.i18n_scope 19 | :query 20 | end 21 | 22 | def self.inspect 23 | +"#<#{self.class.name} @fields=#{fields.inspect}>" 24 | end 25 | end 26 | 27 | # = GraphQL Alternative Query Set 28 | # 29 | # Exact the same as a +FieldSet+ 30 | QuerySet = FieldSet 31 | 32 | # = GraphQL Alternative Mutation Set 33 | # 34 | # Same as a +FieldSet+ but for mutation fields 35 | MutationSet = Class.new(FieldSet) 36 | MutationSet.field_type = GraphQL::Field::MutationField 37 | MutationSet.redefine_singleton_method(:i18n_scope) { :mutation } 38 | 39 | 40 | # = GraphQL Alternative Subscription Set 41 | # 42 | # Same as a +FieldSet+ but for subscription fields 43 | SubscriptionSet = Class.new(FieldSet) 44 | SubscriptionSet.field_type = GraphQL::Field::SubscriptionField 45 | SubscriptionSet.redefine_singleton_method(:i18n_scope) { :subscription } 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/rails/graphql/request/strategy/dynamic_instance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Request 6 | # = GraphQl Strategy Dynamic Instance 7 | # 8 | # When an event is call on non-object types, this class allows both 9 | # finding a method on two different places, the interface or union 10 | # definition, or on the currect object type-class. 11 | class Strategy::DynamicInstance < Helpers::AttributeDelegator 12 | def instance_variable_set(ivar, value) 13 | __getobj__.instance_variable_set(ivar, value) 14 | __current_object__&.instance_variable_set(ivar, value) 15 | end 16 | 17 | private 18 | 19 | def respond_to_missing?(method_name, include_private = false) 20 | __current_object__&.respond_to?(method_name, include_private) || super 21 | end 22 | 23 | def method_missing(method_name, *args, **xargs, &block) 24 | object = __current_object__ 25 | 26 | return super unless object&.respond_to?(method_name) 27 | object.public_send(method_name, *args, **xargs, &block) 28 | end 29 | 30 | def __current_object__ 31 | return unless __getobj__.instance_variable_defined?(:@event) 32 | 33 | event = __getobj__.instance_variable_get(:@event) 34 | return if event.nil? || (object = event.source.try(:current_object)).nil? 35 | 36 | event.strategy.instance_for(object) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/rails/graphql/helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # All helpers that allow this gem to be flexible and extendable to any other 6 | # sources of objects and other gems as well 7 | module Helpers 8 | extend ActiveSupport::Autoload 9 | 10 | autoload :AttributeDelegator 11 | autoload :InheritedCollection 12 | autoload :Instantiable 13 | autoload :LeafFromAr 14 | autoload :Unregisterable 15 | autoload :Registerable 16 | 17 | autoload :WithArguments 18 | autoload :WithAssignment 19 | autoload :WithCallbacks 20 | autoload :WithDirectives 21 | autoload :WithDescription 22 | autoload :WithEvents 23 | autoload :WithFields 24 | autoload :WithGlobalID 25 | autoload :WithName 26 | autoload :WithNamespace 27 | autoload :WithOwner 28 | autoload :WithSchemaFields 29 | autoload :WithValidator 30 | 31 | # Easy way to duplicate objects and set a new owner 32 | def self.dup_all_with_owner(enumerator, owner) 33 | enumerator.map { |item| dup_with_owner(item, owner) }.presence 34 | end 35 | 36 | # Easy way to duplicate a object and set a new owner 37 | def self.dup_with_owner(item, owner) 38 | item.dup.tap { |x| x.instance_variable_set(:@owner, owner) } 39 | end 40 | 41 | # Global helper that merge a hash that contains values as arrays 42 | def self.merge_hash_array(one, other) 43 | one.merge(other) { |_, lval, rval| lval + rval } 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/graphql/type/scalar/bigint_scalar_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Type_Scalar_BigintScalarTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL::Type::Scalar::BigintScalar 5 | LARGE_VALUE = 123456789101112131415161718192021222324252627282930 6 | 7 | def test_valid_input_ask 8 | refute(DESCRIBED_CLASS.valid_input?(nil)) 9 | refute(DESCRIBED_CLASS.valid_input?(1)) 10 | refute(DESCRIBED_CLASS.valid_input?('12.0')) 11 | refute(DESCRIBED_CLASS.valid_input?('1abc')) 12 | 13 | assert(DESCRIBED_CLASS.valid_input?('+123')) 14 | assert(DESCRIBED_CLASS.valid_input?('-123')) 15 | assert(DESCRIBED_CLASS.valid_input?(LARGE_VALUE.to_s)) 16 | end 17 | 18 | def test_valid_output_ask 19 | assert(DESCRIBED_CLASS.valid_output?(1)) 20 | assert(DESCRIBED_CLASS.valid_output?('abc')) 21 | assert(DESCRIBED_CLASS.valid_output?(nil)) 22 | assert(DESCRIBED_CLASS.valid_output?(LARGE_VALUE)) 23 | 24 | refute(DESCRIBED_CLASS.valid_output?([1, 'abc'])) 25 | end 26 | 27 | def test_as_json 28 | assert_equal('1', DESCRIBED_CLASS.as_json(1)) 29 | assert_equal('0', DESCRIBED_CLASS.as_json(nil)) 30 | assert_equal('0', DESCRIBED_CLASS.as_json('a')) 31 | 32 | assert_equal(LARGE_VALUE.to_s, DESCRIBED_CLASS.as_json(LARGE_VALUE)) 33 | end 34 | 35 | def test_deserialize 36 | assert_equal(1, DESCRIBED_CLASS.deserialize(1)) 37 | assert_equal(0, DESCRIBED_CLASS.deserialize('a')) 38 | assert_equal(0, DESCRIBED_CLASS.deserialize(nil)) 39 | 40 | assert_equal(LARGE_VALUE, DESCRIBED_CLASS.deserialize(LARGE_VALUE)) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Hello! This is where you manage which Jekyll version is used to run. 4 | # When you want to use a different version, change it below, save the 5 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 6 | # 7 | # bundle exec jekyll serve 8 | # 9 | # This will help ensure the proper Jekyll version is running. 10 | # Happy Jekylling! 11 | gem "jekyll", "~> 3.9.2" 12 | 13 | # The HTTP server 14 | gem 'webrick', '~> 1.7' 15 | 16 | # If you have any plugins, put them here! 17 | group :jekyll_plugins do 18 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 19 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 20 | # gem 'github-pages', '~> 227' 21 | gem 'jekyll-seo-tag', '~> 2.8' 22 | gem 'jekyll-sitemap', '~> 1.4' 23 | gem 'jekyll-mermaid', '~> 1.0' 24 | gem 'jekyll-toc', '~> 0.18.0' 25 | end 26 | 27 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 28 | # and associated library. 29 | install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do 30 | gem 'tzinfo', '~> 1.2' 31 | gem 'tzinfo-data' 32 | end 33 | 34 | # Performance-booster for watching directories on Windows 35 | gem 'wdm', '~> 0.1.0', install_if: Gem.win_platform? 36 | 37 | # kramdown v2 ships without the gfm parser by default. If you're using 38 | # kramdown v1, comment out this line. 39 | gem 'kramdown-parser-gfm' 40 | 41 | # Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem 42 | # do not have a Java counterpart. 43 | gem 'http_parser.rb', '~> 0.6.0', :platforms => [:jruby] 44 | -------------------------------------------------------------------------------- /lib/generators/graphql/install_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails/generators/base' 4 | 5 | module GraphQL 6 | module Generators 7 | class InstallGenerator < Rails::Generators::Base # :nodoc: 8 | include Rails::GraphQL::BaseGenerator 9 | 10 | desc 'Add an initial setup to your application' 11 | 12 | argument :schema, type: :string, optional: true, 13 | default: "#{APP_MODULE_NAME}Schema", 14 | desc: 'A name for the schema' 15 | 16 | class_option :skip_routes, type: :boolean, 17 | default: false, 18 | desc: 'Add some initial routes' 19 | 20 | class_option :skip_keeps, type: :boolean, 21 | default: false, 22 | desc: 'Skip .keep files' 23 | 24 | def create_config_file 25 | template 'config.rb', 'config/initializers/graphql.rb' 26 | end 27 | 28 | def create_schema 29 | invoke 'graphql:schema' 30 | end 31 | 32 | def create_keep_files 33 | return if options[:skip_keeps] 34 | 35 | %w[ 36 | directives fields sources enums inputs interfaces object 37 | scalars unions queries mutations subscriptions 38 | ].each { |folder| create_file("#{options[:directory]}/#{folder}/.keep") } 39 | end 40 | 41 | def add_routes 42 | return if options[:skip_routes] 43 | route('get "/graphql/describe", to: "graphql/base#describe"') 44 | route('get "/graphiql", to: "graphql/base#graphiql"') 45 | route('post "/graphql", to: "graphql/base#execute"') 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /docs/guides/testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Testing - Guides 4 | description: The built-in features to facilitate testing your GraphQL API 5 | --- 6 | 7 | # Testing 8 | 9 | This gem provides some features that facilitate testing your GraphQL API. 10 | Here is the list of these facilitators: 11 | 12 | {: .important } 13 | > **Important** 14 | > More features will soon be added to both RSpec and Rubocop. 15 | 16 | ## Validating 17 | 18 | You can run a [request](/guides/request) in validation mode, which will go 19 | over the whole `organize` step and return true if the given document, including 20 | context and variables, is valid. 21 | 22 | {: .rails-console } 23 | ```ruby 24 | :001 > GraphQL.valid?('{ welcome }') 25 | => true 26 | ``` 27 | 28 | ## Stubbing Values 29 | 30 | You can easily stub the values received by fields by using 31 | [prepared data](/guides/advanced/request#prepared-data). This feature is not 32 | exclusively for testing, but it was created with this intention. Here are 33 | some examples on how you can use that: 34 | 35 | ```ruby 36 | # Using a simple named argument 37 | GraphQL.execute('{ users { id name } }', data_for: { 38 | 'query.users' => [User.new] 39 | }) 40 | 41 | # Using a more complex setting from the request instance 42 | request = GraphQL.request 43 | request.prepare_data_for('User.id', [1, 2], repeat: :cycle) 44 | request.execute('{ users { id name } }') 45 | ``` 46 | 47 | {: .note } 48 | > The format is always `gql_name.field` or `{query,mutation,subscription}.field` for 49 | > schema fields. 50 | 51 | Read more about [prepared data](/guides/advanced/request#prepared-data). 52 | -------------------------------------------------------------------------------- /lib/rails/graphql/helpers/attribute_delegator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | module Helpers 6 | # This is an extra magic on top of the delegator class from the standard 7 | # lib that allows fetching a specific property of the delegated object 8 | class AttributeDelegator < ::BasicObject 9 | undef_method :== 10 | undef_method :equal? 11 | 12 | def initialize(obj = nil, attribute = nil, cache: true, &block) 13 | @delegate_sd_attr = attribute 14 | @delegate_sd_obj = block.presence || obj 15 | @delegate_cache = cache 16 | end 17 | 18 | def raise(*args) 19 | ::Object.send(:raise, *args) 20 | end 21 | 22 | private 23 | 24 | def respond_to_missing?(method_name, include_private = false) 25 | __getobj__.respond_to?(method_name, include_private) || super 26 | end 27 | 28 | def method_missing(method_name, *args, **xargs, &block) 29 | return super unless __getobj__.respond_to?(method_name) 30 | __getobj__.public_send(method_name, *args, **xargs, &block) 31 | end 32 | 33 | def __getobj__ 34 | @delegate_cache ? (@delegate_ch_obj ||= __buildobj__) : __buildobj__ 35 | end 36 | 37 | def __buildobj__ 38 | result = @delegate_sd_obj 39 | result = result.call if result.respond_to?(:call) 40 | result = result&.public_send(@delegate_sd_attr) if @delegate_sd_attr 41 | result 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/object/input_value_object.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # The introspection object for a input object 7 | class Object::InputValueObject < Object 8 | self.assigned_to = 'Rails::GraphQL::Field::InputField' 9 | self.spec_object = true 10 | 11 | def self.valid_member?(value) 12 | value.is_a?(GraphQL::Argument) || super 13 | end 14 | 15 | delegate :fake_type_object, to: 'Object::TypeObject' 16 | 17 | rename! '__InputValue' 18 | 19 | desc <<~DESC 20 | Arguments provided to Fields or Directives and the input fields of an 21 | InputObject are represented as Input Values which describe their type 22 | and optionally a default value. 23 | DESC 24 | 25 | field :name, :string, null: false, method_name: :gql_name 26 | field :description, :string 27 | field :type, '__Type', null: false, method_name: :build_type 28 | field :default_value, :string 29 | 30 | def default_value 31 | current.to_json if current.default_value? 32 | end 33 | 34 | def build_type 35 | result = current.type_klass 36 | 37 | if current.array? 38 | result = fake_type_object(:non_null, result) unless current.nullable? 39 | result = fake_type_object(:list, result) 40 | end 41 | 42 | result = fake_type_object(:non_null, result) unless current.null? 43 | result 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /docs/guides/advanced/type-assignment.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Type Assignment - Advanced - Guides 4 | description: Assign other classes to types to gain extra powers 5 | --- 6 | 7 | # Type Assignment 8 | 9 | Types like [inputs](/guides/inputs), [interfaces](/guides/interfaces), [objects](/guides/objects), 10 | and [sources](/guides/sources) can be assigned to other classes. This assignment means that 11 | the GraphQL component is directly associated with that other class, and they will cooperate 12 | in providing a smooth interaction. 13 | 14 | ## Setting It 15 | 16 | You can simply set an assignment as following: 17 | 18 | ```ruby 19 | # app/graphql/sources/admin_source.rb 20 | class GraphQL::AdminSource < GraphQL::ARSource 21 | self.assigned_to = 'AdminUser' 22 | ``` 23 | 24 | It is recommended to always assign to the name of the class, so you don't generate 25 | unnecessary loading of constants. 26 | 27 | Read more about [recommendations](/guides/recommendations). 28 | 29 | ## How it Works 30 | 31 | Whenever the GraphQL component needs to check if it is interacting with a value, it will 32 | first check if the value is from the `assigned_class`. 33 | 34 | Some classes, like [sources](/guides/sources), also guarantee that the assigned class 35 | is based on another class to ensure its features are compatible. 36 | 37 | ## Type Map 38 | 39 | Another interesting thing the assignment does is add an alias to the Type Map 40 | for that class to the proper key to find the underlying GraphQL component. That means: 41 | 42 | {: .rails-console } 43 | ```ruby 44 | :001 > Rails::GraphQL.type_map.fetch(User) 45 | => #(val) { val.class <= value } 12 | reverse_each { |item| return item if block.call(item) } 13 | nil 14 | end 15 | 16 | # Check if a given +value+ is included in any of the definitions 17 | def include?(value) 18 | each_definition.any? { |definition| definition.include?(value) } 19 | end 20 | 21 | # If any elements appears, the each block will run and return true 22 | def empty? 23 | lazy.each { return true } 24 | false 25 | end 26 | 27 | # The normal each is the reverse each of the definitions 28 | def each(&block) 29 | lazy.reverse_each(&block) 30 | end 31 | 32 | # The reverse each is the normal each of the definitions 33 | def reverse_each(&block) 34 | block.nil? ? lazy : lazy.each(&block) 35 | end 36 | 37 | # Overrides the lazy operator 38 | def lazy 39 | (@type == :set) ? super.uniq : super 40 | end 41 | 42 | # Allow concatenating objects 43 | def +(other) 44 | result = to_a 45 | result = result.to_set if @type == :set 46 | result + other 47 | end 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/graphql/type_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_TypeTest < GraphQL::TestCase 4 | class DESCRIBED_CLASS < Rails::GraphQL::Type; end 5 | 6 | def test_inherited 7 | assert_equal(DESCRIBED_CLASS, DESCRIBED_CLASS.base_type) 8 | assert_predicate(DESCRIBED_CLASS, :described_class?) 9 | assert_predicate(DESCRIBED_CLASS, :spec_object?) 10 | assert_predicate(DESCRIBED_CLASS, :base_object?) 11 | assert_predicate(DESCRIBED_CLASS, :abstract?) 12 | 13 | other_class = unmapped_class(DESCRIBED_CLASS) 14 | assert_equal(DESCRIBED_CLASS, other_class.base_type) 15 | assert_predicate(other_class, :described_class?) 16 | refute_predicate(other_class, :spec_object?) 17 | refute_predicate(other_class, :base_object?) 18 | refute_predicate(other_class, :abstract?) 19 | end 20 | 21 | def test_equivalence 22 | assert_operator(Rails::GraphQL::Type, :=~, DESCRIBED_CLASS) 23 | assert_operator(Rails::GraphQL::Type, :=~, DESCRIBED_CLASS.new) 24 | assert_operator(DESCRIBED_CLASS, :=~, DESCRIBED_CLASS.new) 25 | 26 | refute_operator(Rails::GraphQL::Type::Scalar, :=~, DESCRIBED_CLASS) 27 | end 28 | 29 | def test_kind 30 | assert_equal(:described_class, DESCRIBED_CLASS.kind) 31 | end 32 | 33 | def test_kind_enum 34 | assert_equal('DESCRIBED_CLASS', DESCRIBED_CLASS.kind_enum) 35 | end 36 | 37 | def test_decorate 38 | assert_equal(1, DESCRIBED_CLASS.decorate(1)) 39 | end 40 | 41 | def test_kind_ask 42 | assert_respond_to(DESCRIBED_CLASS, :scalar?) 43 | assert_respond_to(DESCRIBED_CLASS, :object?) 44 | assert_respond_to(DESCRIBED_CLASS, :interface?) 45 | assert_respond_to(DESCRIBED_CLASS, :union?) 46 | assert_respond_to(DESCRIBED_CLASS, :enum?) 47 | assert_respond_to(DESCRIBED_CLASS, :input?) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/rails/graphql/helpers/with_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | module Helpers 6 | # Helper that contains the main exceptions and validation process for a 7 | # value against a type 8 | module WithValidator 9 | delegate :ordinalize, to: 'ActiveSupport::Inflector' 10 | 11 | protected 12 | 13 | # Run the validation process with +value+ against +type+ 14 | def validate_output!(value, type, checker: :null?, array: true) 15 | result = validate_null(value, checker) 16 | result ||= array? && array \ 17 | ? validate_array(value) \ 18 | : validate_type(value) \ 19 | unless value.nil? 20 | 21 | return if result.blank? 22 | message, idx = result 23 | 24 | base_error = idx.present? \ 25 | ? +"#{ordinalize(idx + 1)} value of the #{gql_name} #{type}" \ 26 | : +"#{gql_name} #{type} value" 27 | 28 | raise InvalidValueError, +"The #{base_error} #{message}." 29 | end 30 | 31 | private 32 | 33 | def validate_array(value) 34 | return 'is not an array' unless value.is_a?(Enumerable) 35 | 36 | value.each_with_index do |val, idx| 37 | err = validate_null(val, :nullable?) || validate_type(val) 38 | return err, idx unless err.nil? 39 | end 40 | end 41 | 42 | def validate_null(value, checker = :null?) 43 | 'can not be null' if value.nil? && !send(checker) 44 | end 45 | 46 | def validate_type(value) 47 | 'is invalid' if leaf_type? && !type_klass.valid_output?(value) 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/graphql_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQLTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Rails::GraphQL 5 | 6 | def test_type_map 7 | DESCRIBED_CLASS.stub_cvar(:@@type_map, 1) do 8 | assert_equal(1, DESCRIBED_CLASS.type_map) 9 | end 10 | 11 | assert_instance_of(Rails::GraphQL::TypeMap, DESCRIBED_CLASS.type_map) 12 | end 13 | 14 | def test_ar_adapter_key 15 | DESCRIBED_CLASS.stub(:config, double(ar_adapters: { 'a' => { key: 1 } })) do 16 | assert_equal(1, DESCRIBED_CLASS.ar_adapter_key('a')) 17 | assert_nil(DESCRIBED_CLASS.ar_adapter_key('b')) 18 | end 19 | end 20 | 21 | def test_enable_ar_adapter 22 | DESCRIBED_CLASS.stub_cvar(:@@loaded_adapters, Set[1]) do 23 | DESCRIBED_CLASS.enable_ar_adapter(1) 24 | pass 'Accepted any value set on loaded_adapters' 25 | end 26 | 27 | value = DESCRIBED_CLASS.get_reset_cvar(:@@loaded_adapters) do 28 | DESCRIBED_CLASS.enable_ar_adapter('PostgreSQL') 29 | end 30 | 31 | assert_includes(value, 'PostgreSQL') 32 | end 33 | 34 | def test_reload_ar_adapters_bang 35 | result = [] 36 | DESCRIBED_CLASS.stub(:enable_ar_adapter, ->(x) { result << x }) do 37 | DESCRIBED_CLASS.stub_cvar(:@@loaded_adapters, Set[]) do 38 | DESCRIBED_CLASS.reload_ar_adapters! 39 | assert_empty(result) 40 | end 41 | 42 | DESCRIBED_CLASS.stub_cvar(:@@loaded_adapters, Set[1]) do 43 | DESCRIBED_CLASS.reload_ar_adapters! 44 | assert_equal([1], result) 45 | end 46 | end 47 | end 48 | 49 | def test_to_gql 50 | DESCRIBED_CLASS.stub_const(:ToGQL, double(compile: passallthrough)) do 51 | assert_equal([1], DESCRIBED_CLASS.to_gql(1)) 52 | assert_equal([1, { a: 1 }], DESCRIBED_CLASS.to_gql(1, a: 1)) 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /docs/_sass/support/mixins/_typography.scss: -------------------------------------------------------------------------------- 1 | @mixin fs-1 { 2 | font-size: $font-size-1 !important; 3 | 4 | @include mq(sm) { 5 | font-size: $font-size-1-sm !important; 6 | } 7 | } 8 | 9 | @mixin fs-2 { 10 | font-size: $font-size-2 !important; 11 | 12 | @include mq(sm) { 13 | font-size: $font-size-3 !important; 14 | } 15 | } 16 | 17 | @mixin fs-3 { 18 | font-size: $font-size-3 !important; 19 | 20 | @include mq(sm) { 21 | font-size: $font-size-4 !important; 22 | } 23 | } 24 | 25 | @mixin fs-4 { 26 | font-size: $font-size-4 !important; 27 | 28 | @include mq(sm) { 29 | font-size: $font-size-5 !important; 30 | } 31 | } 32 | 33 | @mixin fs-5 { 34 | font-size: $font-size-5 !important; 35 | 36 | @include mq(sm) { 37 | font-size: $font-size-6 !important; 38 | } 39 | } 40 | 41 | @mixin fs-6 { 42 | font-size: $font-size-6 !important; 43 | 44 | @include mq(sm) { 45 | font-size: $font-size-7 !important; 46 | line-height: $body-heading-line-height; 47 | } 48 | } 49 | 50 | @mixin fs-7 { 51 | font-size: $font-size-7 !important; 52 | line-height: $body-heading-line-height; 53 | 54 | @include mq(sm) { 55 | font-size: $font-size-8 !important; 56 | } 57 | } 58 | 59 | @mixin fs-8 { 60 | font-size: $font-size-8 !important; 61 | line-height: $body-heading-line-height; 62 | 63 | @include mq(sm) { 64 | font-size: $font-size-9 !important; 65 | } 66 | } 67 | 68 | @mixin fs-9 { 69 | font-size: $font-size-9 !important; 70 | line-height: $body-heading-line-height; 71 | 72 | @include mq(sm) { 73 | font-size: $font-size-10 !important; 74 | } 75 | } 76 | 77 | @mixin fs-10 { 78 | font-size: $font-size-10 !important; 79 | line-height: $body-heading-line-height; 80 | 81 | @include mq(sm) { 82 | font-size: $font-size-10-sm !important; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/test_ext.rb: -------------------------------------------------------------------------------- 1 | # Core ext methods 2 | 3 | class Object < BasicObject 4 | def new_token(value, type) 5 | GQLParser::Token.new(value).tap do |token| 6 | token.instance_variable_set(:@type, type) 7 | end 8 | end 9 | 10 | def stub_ivar(name, value = nil) 11 | instance_variable_set(name, value) 12 | yield 13 | ensure 14 | remove_instance_variable(name) 15 | end 16 | 17 | def stub_cvar(name, value = nil) 18 | class_variable_set(name, value) 19 | yield 20 | ensure 21 | remove_class_variable(name) 22 | end 23 | 24 | def stub_const(name, value) 25 | if const_defined?(name) 26 | old_value = const_get(name) 27 | remove_const(name) 28 | end 29 | 30 | const_set(name, value) 31 | yield 32 | ensure 33 | remove_const(name) 34 | const_set(name, old_value) if defined? old_value 35 | end 36 | 37 | def stub_imethod(name, block) 38 | alias_method(:"_old_#{name}", name) if (reset_old = method_defined?(name)) 39 | define_method(name, &block) 40 | yield 41 | ensure 42 | undef_method(name) 43 | 44 | if reset_old 45 | alias_method(name, :"_old_#{name}") 46 | undef_method(:"_old_#{name}") 47 | end 48 | end 49 | 50 | def get_reset_ivar(name, *extra, &block) 51 | instance_variable_set(name, extra.first) if extra.any? 52 | instance_exec(&block) 53 | 54 | return unless instance_variable_defined?(name) 55 | 56 | instance_variable_get(name).tap do 57 | remove_instance_variable(name) 58 | end 59 | end 60 | 61 | def get_reset_cvar(name, *extra, &block) 62 | class_variable_set(name, extra.first) if extra.any? 63 | instance_exec(&block) 64 | 65 | return unless class_variable_defined?(name) 66 | 67 | class_variable_get(name).tap do 68 | remove_class_variable(name) 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /docs/guides/recommendations.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Recommendations - Guides 4 | description: All the recommendations for making best of this gem 5 | --- 6 | 7 | # Recommendations 8 | 9 | Here you will find all the recommendations for making best of this gem. 10 | 11 | {: .important } 12 | > **Important** 13 | > In the future, items from this list may become 14 | > RuboCop 15 | > cops.
16 | > This section is under construction. 17 | 18 | ## General 19 | 20 | 1. **Never reference a type by their class!**{: .fw-900 } 21 | 1. Always provide a type for fields and arguments, even if it will be resolved properly to `:id` or `:string`; 22 | 1. Use symbol names for scalars and string gql names for anything else; 23 | 1. Do not set arguments using the `arguments` named argument. Open a block and set them up inside of it instead; 24 | 1. Do not define fields on the schema. Use [alternatives](/guides/alternatives) instead; 25 | 1. Provide description for everything, either directly or using [`I18n`](/guides/i18n); 26 | 1. Do not use fields [chaining definition](/guides/fields#chaining-definition); 27 | 1. Avoid using [inline types](/guides/advanced/types#inline-creation), except for unions and enums; 28 | 29 | ## Types 30 | 31 | 1. If a type requires nested fields to be fully qualified, then don't create a scalar; 32 | 1. Always assign a type to a class by its name, not its constant value; 33 | 1. Register all database aliases on the [Type Map](/guides/type-map#aliases) to avoid warnings; 34 | 35 | ## Request 36 | 37 | 1. **Never load data during the resolve stage!**{: .fw-900 } 38 | 1. Use the `prepare` event stage of requests to load data; 39 | 40 | ## Callbacks 41 | 42 | 1. Prefer using `current.something` or `current_value.something` than just calling `something`. You may get some unexpected results. 43 | -------------------------------------------------------------------------------- /docs/guides/objects.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Objects - Guides 4 | description: The principal type of GraphQL to organize fields 5 | --- 6 | 7 | # Objects 8 | 9 | ```graphql 10 | type User { 11 | id 12 | name 13 | } 14 | ``` 15 | 16 | Objects are the principal type of GraphQL. In contrast with [Scalars](/guides/scalars), objects 17 | create branches in the GraphQL tree. It is always through them that you will be able to organize 18 | and access your data. 19 | 20 | ## Creating an Object 21 | 22 | You can define objects on a file or using the shortcut on the schema. 23 | 24 | ```ruby 25 | # app/graphql/objects/user.rb 26 | module GraphQL 27 | class User < GraphQL::Object 28 | field :id 29 | field :name 30 | end 31 | end 32 | 33 | # OR 34 | 35 | # app/graphql/app_schema.rb 36 | object 'User' do 37 | field :id 38 | field :name 39 | end 40 | ``` 41 | 42 | Read more about [field lists](/guides/field-lists). 43 | 44 | {% include type-description.md type="object" name="User" %} 45 | 46 | ### Implementing Interfaces 47 | 48 | Objects can implement interfaces, meaning they will receive a series of imported 49 | fields or comply with what is configured in the interface. 50 | 51 | ```ruby 52 | # app/graphql/objects/user.rb 53 | implements 'Person' 54 | ``` 55 | 56 | Read more about [interfaces](/guides/interfaces). 57 | 58 | {% include type-creators.md type="object" %} 59 | 60 | ## Using Objects 61 | 62 | Once they are defined, you can set them as the type of [output fields](/guides/fields#output-fields). 63 | Then, in your execution document, you can use query any of the fields that were defined. 64 | 65 | ```ruby 66 | field(:recipient, 'User') 67 | ``` 68 | 69 | ```graphql 70 | { recipient { id name } } 71 | ``` 72 | 73 | {: .note } 74 | > Objects is the best place for you to set up your 75 | > Models. 76 | -------------------------------------------------------------------------------- /test/integration/schemas/mysql.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | 3 | class MySQLRecord < ActiveRecord::Base 4 | self.abstract_class = true 5 | 6 | establish_connection( 7 | name: 'mysql', 8 | adapter: 'mysql2', 9 | host: ENV.fetch('GQL_MYSQL_HOST', '127.0.0.1'), 10 | database: ENV.fetch('GQL_MYSQL_DATABASE', 'starwars'), 11 | username: ENV.fetch('GQL_MYSQL_USERNAME', 'root'), 12 | password: ENV['GQL_MYSQL_PASSWORD'], 13 | port: ENV['GQL_MYSQL_PORT'], 14 | ) 15 | end 16 | 17 | MySQLRecord.connection.instance_eval do 18 | create_table 'jedi_types', force: :cascade do |t| 19 | t.string 'name' 20 | end 21 | 22 | create_table 'jedis', force: :cascade do |t| 23 | t.integer 'jedi_type_id' 24 | t.string 'name' 25 | end 26 | end 27 | 28 | class JediType < MySQLRecord 29 | has_many :jedi, class_name: 'Jedi', foreign_key: :jedi_type_id 30 | 31 | accepts_nested_attributes_for :jedi 32 | 33 | MASTER = create!(name: 'Master') 34 | PADAWAN = create!(name: 'Padawan') 35 | end 36 | 37 | class Jedi < MySQLRecord 38 | belongs_to :jedi_type, class_name: 'JediType', foreign_key: :jedi_type_id 39 | 40 | validates :name, presence: true, if: -> { true } 41 | 42 | create!(name: 'Ioda', jedi_type: JediType::MASTER) 43 | create!(name: 'Obi-Wan Kenobi', jedi_type: JediType::MASTER) 44 | create!(name: 'Anakin Skywalker', jedi_type: JediType::PADAWAN) 45 | create!(name: 'Mace Windu', jedi_type: JediType::MASTER) 46 | end 47 | 48 | class StartWarsMySQLSchema < GraphQL::Schema 49 | namespace :star_wars_mysql 50 | 51 | configure do |config| 52 | config.enable_string_collector = false 53 | config.default_response_format = :json 54 | end 55 | 56 | source JediType do 57 | with_options on: 'jediTypes' do 58 | scoped_argument(:order) { |o| order(name: o) } 59 | end 60 | end 61 | 62 | source Jedi 63 | end 64 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: table-wrappers 3 | --- 4 | 5 | 6 | 7 | {% include head.html %} 8 | 9 | {% include icons.html %} 10 |
11 |
12 | 13 | Rails GraphQL 14 | 15 | 16 | 21 | 22 | 25 |
26 | 27 |
28 |
29 | 30 |
31 | {% if page.banner == true %} 32 | {% include banner.html %} 33 | {% endif %} 34 | {% if page.banner == true or page.steps == true %} 35 | {% include 3-steps.html %} 36 | {% endif %} 37 | 38 |
39 | {% include breadcrumb.html %} 40 | 41 | {% if page.toc != false %} 42 | {% include table-of-contents.html content=content %} 43 | {% endif %} 44 | 45 |
46 | {{ content | inject_anchors }} 47 |
48 |
49 |
50 | 51 | 59 | 60 |
61 | 62 | 63 | -------------------------------------------------------------------------------- /lib/rails/graphql/collectors/idented_collector.rb: -------------------------------------------------------------------------------- 1 | module Rails 2 | module GraphQL 3 | module Collectors 4 | # = GraphQL Idented Collector 5 | # 6 | # This collector helps building a indented string, most used for 7 | # displaying a GraphQL schema 8 | class IdentedCollector 9 | def initialize(initial = 0, size = 2, auto_eol: true) 10 | @size = size 11 | @val = [[initial, +'']] 12 | @auto_eol = auto_eol 13 | end 14 | 15 | def indented(start = nil, finish = nil, auto_eol = @auto_eol) 16 | self << start unless start.nil? 17 | 18 | indent 19 | yield 20 | 21 | @val.last.pop while @val.last.last.blank? 22 | unindent 23 | 24 | @val.pop(2) if blank?(-2) 25 | 26 | self << finish unless finish.nil? 27 | eol if auto_eol 28 | self 29 | end 30 | 31 | def value 32 | @val.map do |(ident, *content)| 33 | next if content.size.eql?(1) && content.first.blank? 34 | ident = (' ' * ident) 35 | ident + content.join("\n#{ident}") 36 | end.compact.join("\n") 37 | end 38 | 39 | def puts(str) 40 | @val.last.last << str 41 | eol 42 | end 43 | 44 | def <<(str) 45 | @val.last.last << str 46 | self 47 | end 48 | 49 | def eol 50 | @val.last << '' 51 | self 52 | end 53 | 54 | def indent 55 | return @val.last[0] += @size if blank? 56 | @val << [last_ident + @size, +''] 57 | self 58 | end 59 | 60 | def unindent 61 | return @val.last[0] -= @size if blank? 62 | @val << [last_ident - @size, +''] 63 | self 64 | end 65 | 66 | def last_ident 67 | @val.last.first 68 | end 69 | 70 | def blank?(pos = -1) 71 | @val[pos].size.eql?(2) && @val[pos].last.empty? 72 | end 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /docs/guides/sources/scoped-arguments.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Scoped Arguments - Sources - Guides 4 | description: Share behaviors between source fields using arguments in common 5 | --- 6 | 7 | # Scoped Arguments 8 | 9 | Sources like [Active Record Source](/guides/sources/active-record) use a special 10 | feature that allows you to define shared arguments that implement shared behaviors 11 | between your fields. 12 | 13 | This feature's original intention is to expose models' scopes to several fields. 14 | Due to [proxy fields](/guides/advanced/fields#proxies), you can easily share 15 | scope-based arguments to query fields and association fields ([see here](/guides/sources/active-record#proxy-field)). 16 | 17 | ## How to Use 18 | 19 | You can define scoped arguments at the source-class level, as in: 20 | 21 | ```ruby 22 | # app/graphql/sources/user_source.rb 23 | scoped_argument(:active, :boolean) { |value| where(active: value) } 24 | # ↳ Running under the current object 25 | 26 | # OR, which intends to call the `active` scope on the current object 27 | scoped_argument(:active, :boolean, true) 28 | # The above is designed to work with 29 | scope :active, -> { where(active: true) } 30 | 31 | # OR, using a different scope name and with the argument value 32 | scoped_argument(:active, :boolean, :enabled) 33 | # The above is designed to work with 34 | scope :enabled, ->(value) { where(active: value) } 35 | ``` 36 | 37 | ## Considerations 38 | 39 | {: .highlight } 40 | > **Important** 41 | > This is an experimental feature and may change in the future. 42 | 43 | It's recommended to pass an `on` named argument to limit to which fields that 44 | argument will be added. 45 | 46 | ```ruby 47 | scoped_argument(:active, :boolean, true, on: :users) 48 | # OR 49 | scoped_argument(:active, :boolean, true, on: 'users') 50 | ``` 51 | 52 | Read more about [names](/guides/names). 53 | 54 | A block or method that returns nil won't affect the current value, which will 55 | move forward to the following argument or finish by returning its result. 56 | -------------------------------------------------------------------------------- /docs/_sass/base.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Base element style overrides 3 | // 4 | // stylelint-disable selector-no-type, selector-max-type 5 | 6 | * { 7 | box-sizing: border-box; 8 | } 9 | 10 | ::selection { 11 | color: $white; 12 | background: $link-color; 13 | } 14 | 15 | html { 16 | @include fs-4; 17 | scroll-behavior: smooth; 18 | } 19 | 20 | body { 21 | font-family: $body-font-family; 22 | font-size: inherit; 23 | line-height: $body-line-height; 24 | color: $body-text-color; 25 | background-color: $body-background-color; 26 | } 27 | 28 | ol, 29 | ul, 30 | dl, 31 | pre, 32 | address, 33 | blockquote, 34 | table, 35 | div, 36 | hr, 37 | form, 38 | fieldset, 39 | noscript .table-wrapper { 40 | margin-top: 0; 41 | } 42 | 43 | h1, 44 | h2, 45 | h3, 46 | h4, 47 | h5, 48 | h6 { 49 | margin-top: 0; 50 | margin-bottom: 1em; 51 | font-weight: 500; 52 | line-height: $body-heading-line-height; 53 | color: $body-heading-color; 54 | } 55 | 56 | p { 57 | margin-top: 1em; 58 | margin-bottom: 1em; 59 | } 60 | 61 | a { 62 | color: $link-color; 63 | text-decoration: none; 64 | } 65 | 66 | a:not([class]) { 67 | text-decoration: none; 68 | background-image: linear-gradient($border-color 0%, $border-color 100%); 69 | background-repeat: repeat-x; 70 | background-position: 0 100%; 71 | background-size: 1px 1px; 72 | 73 | &:hover { 74 | background-image: linear-gradient( 75 | rgba($link-color, 0.45) 0%, 76 | rgba($link-color, 0.45) 100% 77 | ); 78 | background-size: 1px 1px; 79 | } 80 | } 81 | 82 | code { 83 | font-family: $mono-font-family; 84 | font-size: 0.75em; 85 | line-height: $body-line-height; 86 | } 87 | 88 | figure, 89 | pre { 90 | margin: 0; 91 | } 92 | 93 | li { 94 | margin: 0.25em 0; 95 | } 96 | 97 | img { 98 | max-width: 100%; 99 | height: auto; 100 | } 101 | 102 | hr { 103 | height: 1px; 104 | padding: 0; 105 | margin: $sp-6 0; 106 | background-color: var(--line-color); 107 | border: 0; 108 | } 109 | -------------------------------------------------------------------------------- /docs/_includes/introspection-query.html: -------------------------------------------------------------------------------- 1 |
2 | Introspection Query 3 |
4 | {% highlight graphql %} 5 | query IntrospectionQuery { 6 | __schema { 7 | queryType { name } 8 | mutationType { name } 9 | subscriptionType { name } 10 | types { 11 | ...FullType 12 | } 13 | directives { 14 | name 15 | description 16 | locations 17 | args { 18 | ...InputValue 19 | } 20 | isRepeatable 21 | } 22 | } 23 | } 24 | 25 | fragment FullType on __Type { 26 | kind 27 | name 28 | description 29 | specifiedByURL 30 | fields(includeDeprecated: true) { 31 | name 32 | description 33 | args { 34 | ...InputValue 35 | } 36 | type { 37 | ...TypeRef 38 | } 39 | isDeprecated 40 | deprecationReason 41 | } 42 | inputFields { 43 | ...InputValue 44 | } 45 | interfaces { 46 | ...TypeRef 47 | } 48 | enumValues(includeDeprecated: true) { 49 | name 50 | description 51 | isDeprecated 52 | deprecationReason 53 | } 54 | possibleTypes { 55 | ...TypeRef 56 | } 57 | } 58 | 59 | fragment InputValue on __InputValue { 60 | name 61 | description 62 | type { ...TypeRef } 63 | defaultValue 64 | } 65 | 66 | fragment TypeRef on __Type { 67 | kind 68 | name 69 | ofType { 70 | kind 71 | name 72 | ofType { 73 | kind 74 | name 75 | ofType { 76 | kind 77 | name 78 | ofType { 79 | kind 80 | name 81 | ofType { 82 | kind 83 | name 84 | ofType { 85 | kind 86 | name 87 | ofType { 88 | kind 89 | name 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } 98 | {% endhighlight %} 99 |
100 |
101 | -------------------------------------------------------------------------------- /lib/rails/graphql/request/arguments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Request 6 | # = GraphQL Request Arguments 7 | # 8 | # This is an extension of an +OpenStruct+ since argument values can be 9 | # assigned a Proc, which means that in order to collect their value, we 10 | # need to rely on the current operation being processed. 11 | # 12 | # They lazy variable-based value is used for fragments, so that they can 13 | # be organized only once and have their variables changed accordingly to 14 | # the spread and operation. 15 | class Arguments < OpenStruct 16 | THREAD_KEY = :_rails_graphql_operation 17 | 18 | class Lazy < Delegator 19 | attr_reader :var_name 20 | 21 | def self.[](key) 22 | new(key) 23 | end 24 | 25 | def initialize(var_name) 26 | @var_name = var_name 27 | end 28 | 29 | def __getobj__ 30 | Arguments.operation&.variables&.dig(var_name) 31 | end 32 | 33 | def __setobj__(*) 34 | raise FrozenError 35 | end 36 | end 37 | 38 | delegate :key?, :[], to: :@table 39 | alias to_hash to_h 40 | 41 | class << self 42 | # Easy access to the easy loader method 43 | def lazy 44 | Lazy 45 | end 46 | 47 | # Get the current operation thread safely 48 | def operation 49 | Thread.current[THREAD_KEY] 50 | end 51 | 52 | # Execute a block inside a scoped thread-safe arguments 53 | def scoped(value) 54 | old_value, Thread.current[THREAD_KEY] = operation, value 55 | 56 | yield 57 | ensure 58 | Thread.current[THREAD_KEY] = old_value 59 | end 60 | 61 | # Check if it's performing inside a scoped value 62 | def scoped? 63 | operation.present? 64 | end 65 | end 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/enum/type_kind_enum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # Bigint basically removes the limit of the value, but it serializes as 7 | # a string so it won't go against the spec 8 | class Enum::TypeKindEnum < Enum 9 | self.spec_object = true 10 | 11 | rename! '__TypeKind' 12 | 13 | desc <<~DESC 14 | The fundamental unit of any GraphQL Schema is the type. 15 | This enum enlist all the valid base types. 16 | DESC 17 | 18 | add 'SCALAR', desc: <<~DESC 19 | Scalar types represent primitive leaf values in a GraphQL type system. 20 | DESC 21 | 22 | add 'OBJECT', desc: <<~DESC 23 | Objects represent a list of named fields, each of which yield a value of a 24 | specific type. 25 | DESC 26 | 27 | add 'INTERFACE', desc: <<~DESC 28 | Interfaces represent a list of named fields and their types. 29 | DESC 30 | 31 | add 'UNION', desc: <<~DESC 32 | Unions represent an object that could be one of a list of GraphQL Object types. 33 | DESC 34 | 35 | add 'ENUM', desc: <<~DESC 36 | Enum types, like scalar types, also represent leaf values in a GraphQL 37 | type system. However Enum types describe the set of possible values. 38 | DESC 39 | 40 | add 'INPUT_OBJECT', desc: <<~DESC 41 | Objects represent a list of named fields, each of which yield a value of 42 | a specific type. 43 | DESC 44 | 45 | add 'LIST', desc: <<~DESC 46 | A GraphQL list is a special collection type which declares the type of 47 | each item in the List (referred to as the item type of the list). 48 | DESC 49 | 50 | add 'NON_NULL', desc: <<~DESC 51 | This type wraps an underlying type, and this type acts identically to that wrapped 52 | type, with the exception that null is not a valid response for the wrapping type. 53 | DESC 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /rails-graphql.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | $LOAD_PATH.push File.expand_path('../lib', __FILE__) 3 | 4 | # Maintain your gem's version: 5 | require 'rails/graphql/version' 6 | require 'date' 7 | 8 | # Describe your gem and declare its dependencies: 9 | Gem::Specification.new do |s| 10 | s.name = 'rails-graphql' 11 | s.version = Rails::GraphQL.version 12 | s.date = Date.today.to_s 13 | s.authors = ['Carlos Silva'] 14 | s.email = ['me@carlosfsilva.com'] 15 | s.homepage = 'https://github.com/virtualshield/rails-graphql' 16 | s.license = 'MIT' 17 | s.summary = 'GraphQL meets RoR with the most Ruby-like DSL' 18 | s.description = 'A Fresh new GraphQL server for Rails applications, with a focus on natural and Ruby-like DSL' 19 | s.metadata = { 20 | 'homepage_uri' => 'https://rails-graphql.dev/', 21 | "source_code_uri" => 'https://github.com/virtualshield/rails-graphql', 22 | 'bug_tracker_uri' => 'https://github.com/virtualshield/rails-graphql/issues', 23 | 'changelog_uri' => 'https://github.com/virtualshield/rails-graphql/blob/master/CHANGELOG.md', 24 | } 25 | 26 | s.require_paths = ['lib'] 27 | 28 | s.files = Dir['MIT-LICENSE', 'README.rdoc', 'lib/**/*', 'ext/**/*', 'Rakefile'] 29 | s.test_files = Dir['test/**/*'] 30 | s.extensions = ['ext/extconf.rb'] 31 | s.rdoc_options = ['--title', 'GraphQL server for Rails'] 32 | 33 | s.required_ruby_version = '>= 2.6.3' 34 | s.add_dependency 'rails', '>= 6.0' 35 | 36 | s.add_development_dependency 'benchmark-ips', '~> 2.8.2' 37 | s.add_development_dependency 'minitest', '~> 5.14.0' 38 | s.add_development_dependency 'minitest-reporters', '~> 1.4.2' 39 | 40 | s.add_development_dependency 'rake-compiler', '~> 1.1.0' 41 | s.add_development_dependency 'rake-compiler-dock', '~> 1.0.1' 42 | 43 | s.add_development_dependency 'rdoc', '~> 6.4.0' 44 | s.add_development_dependency 'simplecov', '~> 0.20' 45 | 46 | s.add_development_dependency 'pg', '~> 1.3' 47 | s.add_development_dependency 'mysql2', '~> 0.5' 48 | s.add_development_dependency 'sqlite3', '~> 1.4' 49 | end 50 | -------------------------------------------------------------------------------- /docs/_includes/features.md: -------------------------------------------------------------------------------- 1 | [GraphQL Parser](/guides/parser) 2 | : Supporting the October 2021 spec 3 | 4 | [Schemas](/guides/schemas) 5 | : One or multiple under the same application or across multiple engines 6 | 7 | [Queries](/guides/queries) 8 | : 3 different ways to defined your queries, besides sources 9 | 10 | [Mutations](/guides/mutations) 11 | : 3 different ways to defined your mutations, besides sources 12 | 13 | [Subscriptions](/guides/subscriptions) 14 | : 3 different ways to defined your subscriptions, besides sources 15 | 16 | [Directives](/guides/directives) 17 | : 3 directives provided: `@deprecated`, `@skip`, `@include` 18 | : Event-driven interface to facilitate new directives 19 | 20 | [Scalars](/guides/scalars) 21 | : All the spec scalars plus: `any`, `bigint`, `binary`, `date`, `date_time`, `decimal`, `json`, and `time` 22 | 23 | [Sources](/guides/sources) 24 | : A bridge between struct-like classes and GraphQL types and fields 25 | : Fully implemented for [ActiveRecord](/guides/sources/active-record) for `PostgreSQL`, `MySQL`, and `SQLite` databases. 26 | 27 | [Shortcuts](/guides/architecture#shortcuts) 28 | : Several shortcuts through `::GraphQL` module to access classes within the gem 29 | 30 | [Type Map](/guides/type-map) 31 | : A centralized place where all the types are stored and can be resolved 32 | 33 | [Global ID](/guides/global-id) 34 | : All objects defined supports `.to_global_id`, or simply `.to_gid` 35 | 36 | [Subscriptions Provider](/guides/subscriptions/providers) 37 | : Current supporting only [ActionCable](/guides/subscriptions/action-cable-provider) provider and [Memory](/guides/subscriptions/memory-store) store 38 | 39 | [Introspection](/guides/introspection) 40 | : All necessary types for introspection with proper descriptions 41 | : Plain text display of the schemas 42 | 43 | [Testing](/guides/testing) 44 | : Support to validate GraphQL documents and stub values before requests 45 | 46 | [Error Handling](/guides/error-handling) 47 | : Full support to `rescue_from` within schemas 48 | : A gracefully backtrace display 49 | -------------------------------------------------------------------------------- /test/graphql/request/context_test.rb: -------------------------------------------------------------------------------- 1 | require 'config' 2 | 3 | class GraphQL_Request_ContextTest < GraphQL::TestCase 4 | DESCRIBED_CLASS = Class.new(Rails::GraphQL::Request::Context) 5 | 6 | def test_stacked 7 | object = DESCRIBED_CLASS.new 8 | object.stacked('a') do 9 | object.stacked('b') do 10 | object.stacked('c') do 11 | object.stacked('d') do 12 | assert_equal('c', object.parent) 13 | assert(object.override_value('e')) 14 | assert_equal('e', object.current_value) 15 | assert_equal(['c', 'b', 'a'], object.ancestors) 16 | end 17 | end 18 | end 19 | end 20 | 21 | object.stacked('a') do |value| 22 | assert_equal('a', object.current_value) 23 | assert_equal('a', value) 24 | 25 | object.current_value = 'b' 26 | assert_equal('b', object.current_value) 27 | assert_equal('b', value) 28 | 29 | object.stacked('c') do 30 | assert_equal('c', object.current_value) 31 | assert_equal('c', value) 32 | 33 | object.current_value = 'e' 34 | assert_equal('e', object.current_value) 35 | assert_equal('e', value) 36 | end 37 | 38 | assert_equal('b', object.current_value) 39 | assert_equal('b', value) 40 | end 41 | end 42 | 43 | def test_parent 44 | object = DESCRIBED_CLASS.new 45 | object.stub_ivar(:@stack, ['a','b']) do 46 | assert_equal('b', object.parent) 47 | end 48 | end 49 | 50 | def test_ancestors 51 | object = DESCRIBED_CLASS.new 52 | object.stub_ivar(:@stack, ['a', 'b', 'c']) do 53 | assert_equal(['b', 'c'], object.ancestors) 54 | end 55 | end 56 | 57 | def test_current_value 58 | object = DESCRIBED_CLASS.new 59 | object.stub_ivar(:@stack, ['b']) do 60 | assert_equal('b', object.current_value) 61 | end 62 | end 63 | 64 | def test_override_value 65 | object = DESCRIBED_CLASS.new 66 | object.stub_ivar(:@stack, ['b']) do 67 | assert_equal('c', object.override_value('c')) 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Unreleased 2 | 3 | ### 1.0.0 4 | 5 | * Allow ENUM inputs to receive string (needs to be enabled through config) 6 | * Setup project's CI 7 | 8 | ### 1.0.0.rc2 - 2023-02-28 9 | 10 | * Fixes for inputs 11 | * Fixes for callbacks and field ownership 12 | * Fixes for the inline type creator and sources 13 | * Fixes to folder structure and constant management 14 | * Provide a better URL for ISO 8601 specification 15 | * Make some constants private 16 | 17 | ### 1.0.0.rc1 - 2023-02-06 18 | 19 | * Added the `@specifiedBy` directive 20 | * Added request extensions 21 | * Base controller and base channel 22 | * An easy to use [GraphiQL](https://github.com/graphql/graphiql) view 23 | * A brand new inline type creator 24 | * Organized several method names to follow one single patter 25 | * Several fixes to events and callbacks 26 | * Fixes for source hooks 27 | * Fixes for scoped arguments 28 | * Docs now available on the [website](https://rails-graphql.dev/) 29 | 30 | ### 1.0.0.beta - 2023-01-23 31 | 32 | * Brand new parser, way faster than the previous one and 12x faster than the original gem 33 | * Subscriptions are now available using ActionCable 34 | * Sources are now built on demand 35 | * A nice backtrace display for when things go wrong 36 | * Methods to validate and compile queries for tests and in preparation for a strict mode feature 37 | * Simple way to provide data to requests for both testing and reuse 38 | * Alternatives are now available: fields can be defined in a standalone class, or in groups, apart from where they will actually live 39 | * Support for persisted queries and several caching features 40 | * Support to ActiveRecord running MySQL 41 | * Fields description can now be defined on I18n 42 | * Way better integration with Zeitwrek. Now the `graphql` folder is 100% compliant with reloader, even though it has its particular structure 43 | * Everything now is compliant with GlobalID, which means that GraphQL objects like fields and directives can be sent to ActiveJob and other places in a serializable way 44 | * TypeMap versioning, so that the application can be updated without tearing down GraphQL 45 | * Lots of performance improvements 46 | -------------------------------------------------------------------------------- /docs/guides/global-id.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Global ID - Guides 4 | description: The customized support of Global ID for GraphQL elements 5 | --- 6 | 7 | # Global ID 8 | 9 | {: .note } 10 | > **Important** 11 | > This is the beginning of a much larger implementation, where pretty much anything would be 12 | > able to be stored as simple Global IDs. 13 | 14 | This gem implements its own version of Rails 15 | Global ID. 16 | The purpose is to identify GraphQL components within your application uniquely. This feature 17 | is widely by [request caching](/guides/advanced/request#caching) and 18 | [request compiling](/guides/advanced/request#compiling). 19 | 20 | ## How it Looks Like 21 | 22 | ```ruby 23 | "gql://base/Type/String" 24 | "gql://base/Schema/query/welcome" 25 | "gql://base/Directive/deprecated" 26 | "gql://base/Directive/deprecated?reason=Just+because" 27 | ``` 28 | 29 | ## The Components 30 | 31 | The URI is can be composed of: 32 | 33 | `schema` 34 | : It will always be `gql` 35 | 36 | `namespace` 37 | : Based on the primary namespace of the component, in 38 | dash format 39 | 40 | `class_name` 41 | : The top-most class responsible for the component 42 | 43 | `scope?` 44 | : For schema fields only, either `query`, `mutation`, or `subscription` 45 | 46 | `name` 47 | : The symbolized name of the object 48 | 49 | `params?` 50 | : A URI-compatible list of parameters 51 | 52 | ## How to Use 53 | 54 | You can get the GID of any element by calling `.to_global_id.to_s` or simply `.to_gid.to_s`. 55 | Then, you can call `GraphQL::GlobalID.find` to get the element of a global id. 56 | 57 | {: .rails-console } 58 | ```ruby 59 | :001 > GraphQL::AppSchema[:query][:welcome].to_gid.to_s 60 | => "gql://base/Schema/query/welcome" 61 | :002 > GraphQL::GlobalID.find("gql://base/Schema/query/welcome") 62 | => # 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/_sass/callouts.scss: -------------------------------------------------------------------------------- 1 | // Callouts 2 | blockquote { 3 | padding: 0.5rem; 4 | position: relative; 5 | isolation: isolate; 6 | padding-left: 1rem; 7 | border-radius: 0 $border-radius $border-radius 0; 8 | 9 | &.new, 10 | &.note, 11 | &.warning, 12 | &.important, 13 | &.highlight { 14 | > p:first-child > b:first-child, 15 | > p:first-child > strong:first-child { 16 | display: block; 17 | font-size: 0.8rem; 18 | letter-spacing: 2px; 19 | margin-bottom: 0.25rem; 20 | text-transform: uppercase; 21 | } 22 | } 23 | 24 | &::before { 25 | inset: 0; 26 | content: ''; 27 | z-index: -1; 28 | opacity: .25; 29 | display: block; 30 | position: absolute; 31 | border-radius: inherit; 32 | } 33 | 34 | &.new { 35 | border-left-color: $green-300; 36 | 37 | &::before { 38 | background-color: $green-000; 39 | } 40 | 41 | > p:first-child > b:first-child, 42 | > p:first-child > strong:first-child { 43 | color: $green-300; 44 | } 45 | } 46 | 47 | &.note { 48 | border-left-color: $blue-300; 49 | 50 | &::before { 51 | background-color: $blue-000; 52 | } 53 | 54 | > p:first-child > b:first-child, 55 | > p:first-child > strong:first-child { 56 | color: $blue-300; 57 | } 58 | } 59 | 60 | &.warning { 61 | border-left-color: $red-300; 62 | 63 | &::before { 64 | background-color: $red-000; 65 | } 66 | 67 | > p:first-child > b:first-child, 68 | > p:first-child > strong:first-child { 69 | color: $red-300; 70 | } 71 | } 72 | 73 | &.important { 74 | border-left-color: $yellow-300; 75 | 76 | &::before { 77 | background-color: $yellow-000; 78 | } 79 | 80 | > p:first-child > b:first-child, 81 | > p:first-child > strong:first-child { 82 | color: $yellow-300; 83 | } 84 | } 85 | 86 | &.highlight { 87 | border-left-color: $purple-300; 88 | 89 | &::before { 90 | background-color: $purple-000; 91 | } 92 | 93 | > p:first-child > b:first-child, 94 | > p:first-child > strong:first-child { 95 | color: $purple-300; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /docs/_sass/code/vs.scss: -------------------------------------------------------------------------------- 1 | // Light 2 | .highlight .c { color: #008000 } /* Comment */ 3 | .highlight .k { color: #0000ff } /* Keyword */ 4 | .highlight .ch { color: #008000 } /* Comment.Hashbang */ 5 | .highlight .cm { color: #008000 } /* Comment.Multiline */ 6 | .highlight .cp { color: #0000ff } /* Comment.Preproc */ 7 | .highlight .cpf { color: #008000 } /* Comment.PreprocFile */ 8 | .highlight .c1 { color: #008000 } /* Comment.Single */ 9 | .highlight .cs { color: #008000 } /* Comment.Special */ 10 | .highlight .ge { font-style: italic } /* Generic.Emph */ 11 | .highlight .gh { font-weight: bold } /* Generic.Heading */ 12 | .highlight .gp { font-weight: bold } /* Generic.Prompt */ 13 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 14 | .highlight .gu { font-weight: bold } /* Generic.Subheading */ 15 | .highlight .kc { color: #0000ff } /* Keyword.Constant */ 16 | .highlight .kd { color: #0000ff } /* Keyword.Declaration */ 17 | .highlight .kn { color: #0000ff } /* Keyword.Namespace */ 18 | .highlight .kp { color: #0000ff } /* Keyword.Pseudo */ 19 | .highlight .kr { color: #0000ff } /* Keyword.Reserved */ 20 | .highlight .kt { color: #2b91af } /* Keyword.Type */ 21 | .highlight .s { color: #a31515 } /* Literal.String */ 22 | .highlight .nc { color: #2b91af } /* Name.Class */ 23 | .highlight .ow { color: #0000ff } /* Operator.Word */ 24 | .highlight .sa { color: #a31515 } /* Literal.String.Affix */ 25 | .highlight .sb { color: #a31515 } /* Literal.String.Backtick */ 26 | .highlight .sc { color: #a31515 } /* Literal.String.Char */ 27 | .highlight .dl { color: #a31515 } /* Literal.String.Delimiter */ 28 | .highlight .sd { color: #a31515 } /* Literal.String.Doc */ 29 | .highlight .s2 { color: #a31515 } /* Literal.String.Double */ 30 | .highlight .se { color: #a31515 } /* Literal.String.Escape */ 31 | .highlight .sh { color: #a31515 } /* Literal.String.Heredoc */ 32 | .highlight .si { color: #a31515 } /* Literal.String.Interpol */ 33 | .highlight .sx { color: #a31515 } /* Literal.String.Other */ 34 | .highlight .sr { color: #a31515 } /* Literal.String.Regex */ 35 | .highlight .s1 { color: #a31515 } /* Literal.String.Single */ 36 | .highlight .ss { color: #a31515 } /* Literal.String.Symbol */ 37 | -------------------------------------------------------------------------------- /docs/_sass/utilities/_layout.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable primer/selector-no-utility, primer/no-override 2 | // 3 | // Utility classes for layout 4 | // 5 | 6 | // Display 7 | 8 | .d-block { 9 | display: block !important; 10 | } 11 | .d-flex { 12 | display: flex !important; 13 | } 14 | .d-inline { 15 | display: inline !important; 16 | } 17 | .d-inline-block { 18 | display: inline-block !important; 19 | } 20 | .d-none { 21 | display: none !important; 22 | } 23 | 24 | @each $media-query in map-keys($media-queries) { 25 | @for $i from 1 through length($spacers) { 26 | @include mq($media-query) { 27 | $size: #{map-get($spacers, sp-#{$i - 1})}; 28 | $scale: #{$i - 1}; 29 | 30 | // .d-sm-block, .d-md-none, .d-lg-inline 31 | .d-#{$media-query}-block { 32 | display: block !important; 33 | } 34 | .d-#{$media-query}-flex { 35 | display: flex !important; 36 | } 37 | .d-#{$media-query}-inline { 38 | display: inline !important; 39 | } 40 | .d-#{$media-query}-inline-block { 41 | display: inline-block !important; 42 | } 43 | .d-#{$media-query}-none { 44 | display: none !important; 45 | } 46 | } 47 | } 48 | } 49 | 50 | // Horizontal alignment 51 | 52 | .float-left { 53 | float: left !important; 54 | } 55 | 56 | .float-right { 57 | float: right !important; 58 | } 59 | 60 | .flex-justify-start { 61 | justify-content: flex-start !important; 62 | } 63 | 64 | .flex-justify-end { 65 | justify-content: flex-end !important; 66 | } 67 | 68 | .flex-justify-between { 69 | justify-content: space-between !important; 70 | } 71 | 72 | .flex-justify-around { 73 | justify-content: space-around !important; 74 | } 75 | 76 | // Vertical alignment 77 | 78 | .v-align-baseline { 79 | vertical-align: baseline !important; 80 | } 81 | .v-align-bottom { 82 | vertical-align: bottom !important; 83 | } 84 | .v-align-middle { 85 | vertical-align: middle !important; 86 | } 87 | .v-align-text-bottom { 88 | vertical-align: text-bottom !important; 89 | } 90 | .v-align-text-top { 91 | vertical-align: text-top !important; 92 | } 93 | .v-align-top { 94 | vertical-align: top !important; 95 | } 96 | -------------------------------------------------------------------------------- /lib/rails/graphql/directive/deprecated_directive.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # = GraphQL Spec Deprecated Directive 6 | # 7 | # Mark fields or enum values as deprecated which will include an error 8 | # message when they are requested or returned 9 | class Directive::DeprecatedDirective < Directive 10 | self.spec_object = true 11 | 12 | placed_on :field_definition, :enum_value 13 | 14 | desc <<~DESC 15 | Indicate deprecated portions of a GraphQL service's schema, such as deprecated 16 | fields on a type or deprecated enum values. 17 | DESC 18 | 19 | argument :reason, :string, desc: <<~DESC 20 | Explain why the underlying element was marked as deprecated. If possible, 21 | indicate what element should be used instead. This description is formatted 22 | using Markdown syntax (as specified by [CommonMark](http://commonmark.org/)). 23 | DESC 24 | 25 | on(:organized) do |event| 26 | report_for_field(event) 27 | end 28 | 29 | on(:finalize, for: Type::Enum) do |event| 30 | report_for_enum_value(event) 31 | end 32 | 33 | private 34 | 35 | # Check if the requested field is marked as deprecated 36 | def report_for_field(event) 37 | return unless event.field.using?(self.class) 38 | item = +"#{event.source.gql_name} field" 39 | event.request.report_error(build_message(item)) 40 | end 41 | 42 | # Check if the resolved enum value is marked as deprecated 43 | def report_for_enum_value(event) 44 | return unless event.current_value.deprecated? 45 | 46 | value = event.current_value.to_s 47 | item = +"#{value} value for the #{event.source.gql_name} field" 48 | event.request.report_error(build_message(item)) 49 | end 50 | 51 | # Build the error message to display on the result 52 | def build_message(item) 53 | result = +"The #{item} is deprecated" 54 | result << ", reason: #{args.reason}" if args.reason.present? 55 | result << '.' 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /benchmarks/star_wars.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/inline' 4 | 5 | gemfile do 6 | source 'https://rubygems.org' 7 | gem 'benchmark-memory', require: 'benchmark/memory' 8 | gem 'benchmark-ips', require: 'benchmark/ips' 9 | gem 'pry-byebug' 10 | 11 | gem 'rails', '>= 5.0' 12 | gem 'graphql' 13 | gem 'rails-graphql', path: '../' 14 | end 15 | 16 | require 'rails' 17 | load(Pathname.new(__dir__).join("../tmp/star_wars.rb")) 18 | 19 | DISPLAY = false 20 | ARGS = { aid: '1000', bid: '2000' } 21 | QUERY = <<~GQL 22 | query($aid: ID!, $bid: ID) { 23 | human(id: $aid) { ...data } 24 | droid(id: $bid) { ...data } 25 | } 26 | 27 | fragment data on Character { 28 | __typename 29 | id 30 | name 31 | appearsIn 32 | ... on Human { 33 | homePlanet 34 | } 35 | ... on Droid { 36 | primaryFunction 37 | } 38 | friends @include(if: true) { 39 | __typename 40 | id 41 | name 42 | appearsIn 43 | ... on Human { 44 | homePlanet 45 | } 46 | ... on Droid { 47 | primaryFunction 48 | } 49 | } 50 | } 51 | GQL 52 | 53 | ogem = -> do 54 | require 'graphql' 55 | require_relative 'star_wars/original_gem' 56 | end 57 | 58 | ngem = -> do 59 | require 'rails-graphql' 60 | require_relative 'star_wars/new_gem' 61 | 62 | Rails::GraphQL.eager_load! 63 | Rails::GraphQL.type_map.send(:register_pending!) 64 | Rails::GraphQL.config.logger = ActiveSupport::TaggedLogging.new(Logger.new('/dev/null')) 65 | end 66 | 67 | Benchmark.ips do |x| 68 | x.report('Original gem') { ogem.call; StarWars.execute(QUERY, ARGS, display: DISPLAY) } 69 | x.report('New gem') { ngem.call; StarWarsSchema.execute(QUERY, ARGS, display: DISPLAY) } 70 | x.compare! 71 | end 72 | 73 | # Benchmark.memory do |x| 74 | # x.report('Original gem') { ogem.call; StarWars.execute(QUERY, ARGS, display: DISPLAY) } 75 | # x.report('New gem') { ngem.call; StarWarsSchema.execute(QUERY, ARGS, display: DISPLAY) } 76 | # x.compare! 77 | # end 78 | 79 | # require 'memory_profiler' 80 | # MemoryProfiler.report(allow_files: 'rails/graphql') do 81 | # StarWarsSchema.execute(QUERY, ARGS, display: DISPLAY) 82 | # end.pretty_print 83 | -------------------------------------------------------------------------------- /docs/guides/subscriptions/action-cable-provider.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Action Cable Provider - Providers - Subscriptions - Guides 4 | description: Deliver GraphQL requests, including subscriptions, through Action Cable 5 | --- 6 | 7 | # Action Cable Provider 8 | 9 | The Action Cable Provider uses a channel to communicate with your front end using a web socket. 10 | First, you must ensure that you have properly set up 11 | Action Cable. 12 | Then you can use the provided 13 | `GraphQL::BaseChannel` 14 | or implement your own, taking advantage of the 15 | `Rails::GraphQL::Channel` 16 | concern. 17 | 18 | The concern was designed to be easily overridden, allowing you to decide which parts 19 | you want to use and which ones you need to add or change the settings. For example, here is 20 | how you can add your own context: 21 | 22 | ```ruby 23 | # app/channels/graphql_channel.rb 24 | class GraphQLChannel < ApplicationCable::Channel 25 | include GraphQL::Channel 26 | 27 | protected 28 | 29 | def gql_context(*) 30 | super.merge(current_user: current_user) 31 | end 32 | end 33 | ``` 34 | 35 | Read more about [customizing the Channel](/guides/customizing/channel). 36 | 37 | ## How it Works 38 | 39 | The provider uses the Action Cable server and its underlying pub-sub mechanism 40 | to stream subsequent results from subscriptions. It will create one stream per 41 | subscription plus an internal asynchronous callback. 42 | 43 | Here is everything that you can configure for this provider: 44 | 45 | `cable` 46 | : `::ActionCable` - The Action Cable class 47 | 48 | `prefix` 49 | : `rails-graphql` - The streams prefix 50 | 51 | `store` 52 | : [`Rails::GraphQL::Subscription::Store::Memory.new`](/guides/subscriptions/memory-store) - The store of the subscriptions 53 | 54 | `logger` 55 | : `Rails::GraphQL.logger` - The logger 56 | -------------------------------------------------------------------------------- /examples/definitions.rb: -------------------------------------------------------------------------------- 1 | class GraphQL::Point2dInput < GraphQL::Input 2 | desc 'A geometry point with +x+ and +y+ values' 3 | 4 | field :x, :integer, null: false, desc: 'The +x+ value' 5 | field :y, :integer, null: false, desc: 'The +y+ value' 6 | end 7 | 8 | class GraphQL::NamedInterface < GraphQL::Interface 9 | desc 'Any entity that has first and last name' 10 | 11 | field :first_name, :string, null: false 12 | field :last_name, :string, null: false 13 | end 14 | 15 | class GraphQL::AgedInterface < GraphQL::Interface 16 | desc 'Any entity that has an age field' 17 | 18 | field :age, :integer, null: true 19 | end 20 | 21 | class GraphQL::UserObject < GraphQL::Object 22 | desc 'Simple information about an user' 23 | 24 | implements :named, :aged 25 | overwrite_field :age, null: false, desc: "The user's age" 26 | 27 | field :birthdate, :date, null: false, desc: "The user's birthdate" 28 | end 29 | 30 | class GraphQL::SampleObject < GraphQL::Object 31 | desc 'Test for use with symbol' 32 | 33 | field :old_ids, :id, full: true do 34 | desc 'The old list of ids' 35 | 36 | use :deprecated, reason: 'Use the +newIds+ instead, it is faster' 37 | 38 | argument :odd_only, :bool, default: false 39 | argument :even_only, :bool, default: false 40 | end 41 | 42 | field :new_ids, :id, full: true do 43 | desc 'The new list of ids' 44 | 45 | argument :odd_only, :bool, default: false 46 | argument :even_only, :bool, default: false 47 | end 48 | end 49 | 50 | class GraphQL::OtherSampleObject < GraphQL::Object 51 | desc 'Test proxy fields' 52 | 53 | proxy_field GraphQL::SampleObject[:new_ids] 54 | end 55 | 56 | class GraphQL::SimpleArgsClass < GraphQL::Object 57 | desc 'An object that uses the simple definition of arguments' 58 | 59 | field :items_plus, :string, array: true, 60 | arguments: arg(:odd, :boolean) + arg(:even, :boolean, default: false) 61 | 62 | field :items_e, :string, array: true, 63 | arguments: arg(:odd, :boolean) & arg(:even, :boolean, default: false) 64 | end 65 | 66 | class GraphQL::DeletePostMutation < GraphQL::Mutation 67 | desc 'Removes a post record based on its id' 68 | 69 | id_argument 70 | 71 | returns :boolean, null: false 72 | 73 | def perform 74 | true 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/rails/graphql/request/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Request 6 | # = GraphQL Request Errors 7 | # 8 | # This class is inspired by +ActiveModel::Errors+. The idea is to hold all 9 | # the errors that happened during the execution of a request. It also 10 | # helps to export such information to the result object. 11 | class Errors 12 | include Enumerable 13 | 14 | delegate :empty?, :size, :each, :to_json, :last, :first, to: :@items 15 | 16 | def initialize(request) 17 | @request = request 18 | @items = [] 19 | end 20 | 21 | def reset! 22 | @items = [] 23 | end 24 | 25 | # Return a deep duplicated version of the items 26 | def to_a 27 | @items.deep_dup 28 | end 29 | 30 | # Add +message+ to the list of errors. Any other keyword argument will 31 | # be used on set on the +:extensions+ part. 32 | # 33 | # ==== Options 34 | # 35 | # * :line - The line associated with the error. 36 | # * :col - The column associated with the error. 37 | # * :path - The path of the field that generated the error. 38 | def add(message, line: nil, col: nil, path: nil, **extra) 39 | item = { 'message' => message } 40 | 41 | item['locations'] = extra.delete(:locations) 42 | item['locations'] ||= [{ line: line.to_i, column: col.to_i }] \ 43 | if line.present? && col.present? 44 | 45 | item['path'] = path if path.present? && path.is_a?(::Array) 46 | item['extensions'] = extra.deep_stringify_keys if extra.present? 47 | item['locations']&.map!(&:stringify_keys) 48 | 49 | @items << item.compact 50 | end 51 | 52 | # Dump the necessary information from errors to a cached operation 53 | def cache_dump 54 | @items.select { |item| item.dig('extensions', 'stage') == 'organize' } 55 | end 56 | 57 | # Load the necessary information from a cached request data 58 | def cache_load(data) 59 | @items += data 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/rails/graphql/introspection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | # Module related to some methods regarding the introspection of a schema 6 | module Introspection 7 | # When register is called, add introspection related elements 8 | def register!(*) 9 | super if defined? super 10 | enable_introspection! if !introspection? && config.enable_introspection 11 | 12 | # Although this is not necessary besides for introspection, there is no 13 | # real disadvantage on adding it 14 | Helpers::WithSchemaFields::TYPE_FIELD_CLASS.each_key do |type| 15 | GraphQL.type_map.register_alias(type_name_for(type), namespace: namespace) do 16 | result = public_send(:"#{type}_type") 17 | type.eql?(:query) || result.present? ? result : nil 18 | end 19 | end 20 | end 21 | 22 | # Check if the schema has introspection enabled 23 | def introspection? 24 | false 25 | end 26 | 27 | protected 28 | 29 | # Enable introspection fields 30 | def enable_introspection! 31 | redefine_singleton_method(:introspection?) { true } 32 | introspection_dependencies! 33 | 34 | safe_add_field(:query, :__schema, '__Schema', null: false) do 35 | resolve { schema } 36 | end 37 | 38 | safe_add_field(:query, :__type, '__Type') do 39 | argument(:name, :string, null: false) 40 | resolve { schema.find_type(argument(:name)) } 41 | end 42 | end 43 | 44 | # Add the introspection dependencies, but only when necessary 45 | def introspection_dependencies! 46 | GraphQL.type_map.add_dependencies([ 47 | "#{__dir__}/type/enum/directive_location_enum", 48 | "#{__dir__}/type/enum/type_kind_enum", 49 | 50 | "#{__dir__}/type/object/directive_object", 51 | "#{__dir__}/type/object/enum_value_object", 52 | "#{__dir__}/type/object/field_object", 53 | "#{__dir__}/type/object/input_value_object", 54 | "#{__dir__}/type/object/schema_object", 55 | "#{__dir__}/type/object/type_object", 56 | ], to: :base) 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/rails/graphql/request/strategy/cached_strategy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Request 6 | # = GraphQl Cached Strategy 7 | # 8 | # This strategy will process hard cached operations. Soft cached 9 | # operations are those that only the document is cached in the server and 10 | # processed via its unique identifier (UUID). Whereas, hard cached 11 | # operations pretty muchy skips the organize step since that is what is 12 | # cached. 13 | # 14 | # Beware, if the version in the cache is different from the version in the 15 | # type map, it won't be able to process it. 16 | class Strategy::CachedStrategy < Strategy 17 | self.priority = 100 18 | 19 | class << self 20 | 21 | # Resolve whenever it has a cache directive on any of the operations 22 | def can_resolve?(request) 23 | false 24 | # request.operations.each_value.any? do |op| 25 | # op.data&.directives&.any? { |dir| directive_name(dir) == 'cached' } 26 | # end 27 | end 28 | 29 | private 30 | 31 | def directive_name(obj) 32 | Native.node_name(Native.directive_name(obj)) 33 | end 34 | end 35 | 36 | # Executes the strategy in the normal mode 37 | def resolve! 38 | response.with_stack('data') do 39 | for_each_operation { |op| collect_listeners { op.organize! } } 40 | for_each_operation { |op| collect_data { op.prepare! } } 41 | for_each_operation { |op| collect_response { op.resolve! } } 42 | 43 | # collect_data(true) { op.prepare! } 44 | # collect_response { op.resolve! } 45 | 46 | # operations.each_value do |op| 47 | # collect_listeners { op.organize! } 48 | # collect_data(true) { op.prepare! } 49 | # collect_response { op.resolve! } 50 | # end 51 | end 52 | end 53 | 54 | private 55 | 56 | # Execute a given block for each defined operation 57 | def for_each_operation(&block) 58 | operations.each_value(&block) 59 | end 60 | 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /benchmarks/star_wars/original_gem.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module StarWars 4 | class EpisodeEnum < GraphQL::Schema::Enum 5 | description 'One of the films in the Star Wars Trilogy' 6 | 7 | value 'NEW_HOPE', 'Released in 1977.' 8 | value 'EMPIRE', 'Released in 1980.' 9 | value 'JEDI', 'Released in 1983.' 10 | end 11 | 12 | module CharacterInterface 13 | include GraphQL::Schema::Interface 14 | 15 | graphql_name 'Character' 16 | description 'A character in the Star Wars Trilogy' 17 | 18 | field :id, ID, null: false, 19 | description: 'The id of the character' 20 | 21 | field :name, String, null: true, 22 | description: 'The name of the character' 23 | 24 | field :friends, [CharacterInterface], null: true, 25 | description: 'The friends of the character, or an empty list if they have none' 26 | 27 | field :appears_in, [EpisodeEnum], null: true, 28 | description: 'Which movies they appear in' 29 | 30 | definition_methods do 31 | def resolve_type(object, *) 32 | object.is_a?(::Human) ? HumanType : DroidType 33 | end 34 | end 35 | end 36 | 37 | class HumanType < GraphQL::Schema::Object 38 | implements CharacterInterface 39 | graphql_name 'Human' 40 | 41 | field :home_planet, String, null: true, 42 | description: 'The home planet of the human, or null if unknown' 43 | end 44 | 45 | class DroidType < GraphQL::Schema::Object 46 | implements CharacterInterface 47 | graphql_name 'Droid' 48 | 49 | field :primary_function, String, null: true, 50 | description: 'The primary function of the droid' 51 | end 52 | 53 | class QueryType < GraphQL::Schema::Object 54 | graphql_name 'Query' 55 | 56 | field :human, HumanType, null: false do 57 | argument :id, ID, required: true 58 | end 59 | 60 | field :droid, DroidType, null: false do 61 | argument :id, ID, required: true 62 | end 63 | 64 | def human(id:) 65 | STAR_WARS_DATA[:humans][id] 66 | end 67 | 68 | def droid(id:) 69 | STAR_WARS_DATA[:droids][id] 70 | end 71 | end 72 | 73 | class Schema < GraphQL::Schema 74 | query(QueryType) 75 | end 76 | 77 | def self.execute(query, args, display: false) 78 | result = Schema.execute(query, variables: args).to_json 79 | puts result if display 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/rails/graphql/type/object/field_object.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Rails 4 | module GraphQL 5 | class Type 6 | # The introspection object for a field on objects and interfaces 7 | class Object::FieldObject < Object 8 | self.assigned_to = 'Rails::GraphQL::Field' 9 | self.spec_object = true 10 | 11 | delegate :fake_type_object, to: 'Object::TypeObject' 12 | 13 | rename! '__Field' 14 | 15 | desc <<~DESC 16 | Fields are the elements that compose both Objects and Interfaces. Each 17 | field in these other objects may contain arguments and always yields 18 | a value of a specific type. 19 | DESC 20 | 21 | field :name, :string, null: false, method_name: :gql_name 22 | field :description, :string 23 | field :args, '__InputValue', full: true 24 | field :type, '__Type', null: false, method_name: :build_type 25 | field :is_deprecated, :boolean, null: false, method_name: :deprecated? 26 | field :deprecation_reason, :string 27 | 28 | def description 29 | if current.method(:description).arity == 0 30 | current.description 31 | else 32 | current.description(schema.namespace) 33 | end 34 | end 35 | 36 | def build_type 37 | result = current.type_klass 38 | 39 | if current.array? 40 | result = fake_type_object(:non_null, result) unless current.nullable? 41 | result = fake_type_object(:list, result) 42 | end 43 | 44 | result = fake_type_object(:non_null, result) unless current.null? 45 | result 46 | end 47 | 48 | def args 49 | all_arguments&.values || EMPTY_ARRAY 50 | end 51 | 52 | def deprecated? 53 | !deprecated_instance.nil? 54 | end 55 | 56 | def deprecation_reason 57 | deprecated_instance&.args&.reason 58 | end 59 | 60 | private 61 | 62 | def deprecated_instance 63 | current.all_directives&.reverse_each do |item| 64 | return item if item.class <= deprecated_directive 65 | end 66 | 67 | nil 68 | end 69 | end 70 | end 71 | end 72 | end 73 | --------------------------------------------------------------------------------