graphql-extras 0.2.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e0eea726ab643425fcc8b63f518a757a132549b2f4d7bc15fa9be79ac2cf830
4
- data.tar.gz: '08847c52f2c42c505e8201147c3826adebf21f33919385d4985cb6f334300493'
3
+ metadata.gz: 1ad1e3740a8a7ef06a6dad31a459153a121e254824225bf1a12c2718a59a699f
4
+ data.tar.gz: 36ce81c3f1f0888924668ccef5bde169958e45a97837cc669d94a416edc573de
5
5
  SHA512:
6
- metadata.gz: 41487444c67cba8707b0fc8bea877bcb9ad659f6d3732e2a28a79823d451ba20bf7ccb32a9d3c3e739dd8ba6237423c661f70fe3bf0a7a543bdae870e3ef1091
7
- data.tar.gz: 3c2557eefb6bcd90f68a09ac019fa967743c0cd89a23f0bdce1708363daf26877d53a975aa11057bde08d9b0b3f4bc8e3d18989f61083648cd6bef90448cb256
6
+ metadata.gz: ba8b4d40badcfba328280f397f8fb7ab7f42ce5257478c0482e48c4f0abf533f4b170d51f507caf6378ed6239d51460b4fe0f4623a6cfb93e0b565bbbf81a80d
7
+ data.tar.gz: de3c6a7f726b0b9ca1311906c1c3d05c0896fe8ba1b64d9d059cc0b7c5d82c227ae7ec17611b999b666201b97818daea30d2f996fda5f9d9fefa849d00ec9fc2
@@ -0,0 +1,25 @@
1
+ name: Build
2
+ on: [push]
3
+ jobs:
4
+ build:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - name: Checkout
8
+ uses: actions/checkout@v2
9
+
10
+ - name: Setup Ruby
11
+ uses: actions/setup-ruby@v1
12
+ with:
13
+ ruby-version: 2.6.x
14
+
15
+ - name: Install packages
16
+ run: sudo apt-get install libsqlite3-dev
17
+
18
+ - name: Install bundler
19
+ run: gem install bundler
20
+
21
+ - name: Install dependencies
22
+ run: bundle install
23
+
24
+ - name: Test
25
+ run: bundle exec rspec
@@ -0,0 +1,41 @@
1
+ name: Publish
2
+ on:
3
+ release:
4
+ types: [published]
5
+ jobs:
6
+ publish:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - name: Checkout
10
+ uses: actions/checkout@v2
11
+
12
+ - name: Setup Ruby
13
+ uses: actions/setup-ruby@v1
14
+ with:
15
+ ruby-version: 2.6.x
16
+
17
+ - name: Install packages
18
+ run: sudo apt-get install libsqlite3-dev
19
+
20
+ - name: Install bundler
21
+ run: gem install bundler
22
+
23
+ - name: Install dependencies
24
+ run: bundle install
25
+
26
+ - name: Test
27
+ run: bundle exec rspec
28
+
29
+ - name: Set version
30
+ run: perl -pi -e "s/0\.0\.0/${GITHUB_REF:11}/" lib/graphql/extras/version.rb
31
+
32
+ - name: Publish
33
+ run: |
34
+ mkdir -p $HOME/.gem
35
+ touch $HOME/.gem/credentials
36
+ chmod 0600 $HOME/.gem/credentials
37
+ printf -- "---\n:rubygems_api_key: ${RUBYGEMS_TOKEN}\n" > $HOME/.gem/credentials
38
+ gem build *.gemspec
39
+ gem push *.gem
40
+ env:
41
+ RUBYGEMS_TOKEN: ${{ secrets.RUBYGEMS_TOKEN }}
data/README.md CHANGED
@@ -1,7 +1,30 @@
1
- # GraphQL::Extras [![Build Status](https://travis-ci.org/rzane/graphql-extras.svg?branch=master)](https://travis-ci.org/rzane/graphql-extras)
1
+ <h1 align="center">GraphQL::Extras</h1>
2
+
3
+ <div align="center">
4
+
5
+ ![Build](https://github.com/rzane/graphql-extras/workflows/Build/badge.svg)
6
+ ![Version](https://img.shields.io/gem/v/graphql-extras)
7
+
8
+ </div>
2
9
 
3
10
  A collection of utilities for building GraphQL APIs.
4
11
 
12
+ **Table of Contents**
13
+
14
+ - [Installation](#installation)
15
+ - [Usage](#usage)
16
+ - [GraphQL::Extras::Controller](#graphqlextrascontroller)
17
+ - [GraphQL::Extras::Preload](#graphqlextraspreload)
18
+ - [GraphQL::Extras::PreloadSource](#graphqlextraspreloadsource)
19
+ - [GraphQL::Extras::Types](#graphqlextrastypes)
20
+ - [Date](#date)
21
+ - [DateTime](#datetime)
22
+ - [Decimal](#decimal)
23
+ - [Upload](#upload)
24
+ - [GraphQL::Extras::Test](#graphqlextrastest)
25
+ - [Development](#development)
26
+ - [Contributing](#contributing)
27
+
5
28
  ## Installation
6
29
 
7
30
  Add this line to your application's Gemfile:
@@ -30,34 +53,44 @@ class GraphqlController < ApplicationController
30
53
  end
31
54
  ```
32
55
 
33
- ### GraphQL::Extras::Batch::AssociationLoader
56
+ ### GraphQL::Extras::Preload
34
57
 
35
- This is a subclass of [`GraphQL::Batch::Loader`](https://github.com/Shopify/graphql-batch) that performs eager loading of Active Record associations.
58
+ This allows you to preload associations before resolving fields.
36
59
 
37
60
  ```ruby
38
- loader = GraphQL::Extras::Batch::AssociationLoader.for(:blog)
39
- loader.load(Post.first)
40
- loader.load_many(Post.all)
41
- ```
42
-
43
- ### GraphQL::Extras::Batch::Resolvers
61
+ class Schema < GraphQL::Schema
62
+ use GraphQL::Dataloader
63
+ end
44
64
 
45
- This includes a set of convenience methods for query batching.
65
+ class BaseField < GraphQL::Schema::Field
66
+ prepend GraphQL::Extras::Preload
67
+ end
46
68
 
47
- ```ruby
48
- class Post < GraphQL::Schema::Object
49
- include GraphQL::Extras::Batch::Resolver
69
+ class BaseObject < GraphQL::Schema::Object
70
+ field_class BaseField
71
+ end
50
72
 
51
- field :blog, BlogType, resolve: association(:blog), null: false
52
- field :comments, [CommentType], resolve: association(:comments, preload: { comments: :user }), null: false
53
- field :blog_title, String, null: false
73
+ class PostType < BaseObject
74
+ field :author, AuthorType, preload: :author, null: false
75
+ field :author_posts, [PostType], preload: {author: :posts}, null: false
76
+ field :depends_on_author, Integer, preload: :author, null: false
54
77
 
55
- def blog_title
56
- association(object, :blog).then(&:title)
78
+ def author_posts
79
+ object.author.posts
57
80
  end
58
81
  end
59
82
  ```
60
83
 
84
+ ### GraphQL::Extras::PreloadSource
85
+
86
+ This is a subclass of [`GraphQL::Dataloader::Source`](https://graphql-ruby.org/dataloader/overview.html) that performs eager loading of Active Record associations.
87
+
88
+ ```ruby
89
+ loader = dataloader.with(GraphQL::Extras::PreloadSource, :blog)
90
+ loader.load(Post.first)
91
+ loader.load_many(Post.all)
92
+ ```
93
+
61
94
  ### GraphQL::Extras::Types
62
95
 
63
96
  In your base classes, you should include the `GraphQL::Extras::Types`.
@@ -88,7 +121,7 @@ This scalar takes a `DateTime` and transmits it as a string, using ISO 8601 form
88
121
  field :created_at, DateTime, required: true
89
122
  ```
90
123
 
91
- *Note: This is just an alias for the `ISO8601DateTime` type that is included in the `graphql` gem.*
124
+ _Note: This is just an alias for the `ISO8601DateTime` type that is included in the `graphql` gem._
92
125
 
93
126
  #### Decimal
94
127
 
@@ -119,36 +152,44 @@ Take note of the correspondence between the value `"image"` and the additional H
119
152
 
120
153
  See [apollo-link-upload](https://github.com/rzane/apollo-link-upload) for the client-side implementation.
121
154
 
122
- ### RSpec integration
155
+ ### GraphQL::Extras::Test
156
+
157
+ This module makes it really easy to test your schema.
123
158
 
124
- Add the following to your `rails_helper.rb` (or `spec_helper.rb`).
159
+ First, create a test schema:
125
160
 
126
161
  ```ruby
127
- require "graphql/extras/rspec"
162
+ # spec/support/test_schema.rb
163
+ require "graphql/extras/test"
164
+
165
+ class TestSchema < GraphQL::Extras::Test::Schema
166
+ configure schema: Schema, queries: "spec/**/*.graphql"
167
+ end
128
168
  ```
129
169
 
130
170
  Now, you can run tests like so:
131
171
 
132
172
  ```ruby
173
+ require "support/test_schema"
174
+
133
175
  RSpec.describe "hello" do
134
176
  let(:context) { { name: "Ray" } }
135
- let(:schema) { use_schema(Schema, context: context) }
136
- let(:queries) { graphql_fixture("hello.graphql") }
177
+ let(:schema) { TestSchema.new(context) }
137
178
 
138
179
  it "allows easily executing queries" do
139
- result = schema.execute(queries.hello)
180
+ query = schema.hello
140
181
 
141
- expect(result).to be_successful_query
142
- expect(result['data']['hello']).to eq("world")
182
+ expect(query).to be_successful
183
+ expect(query.data["hello"]).to eq("world")
143
184
  end
144
- end
145
- ```
146
185
 
147
- The `graphql_fixture` method assumes that your queries live in `spec/fixtures/graphql`. You can change this assumption with the following configuration:
186
+ it "parses errors" do
187
+ query = schema.kaboom
148
188
 
149
- ```ruby
150
- RSpec.configure do |config|
151
- config.graphql_fixture_path = '/path/to/queries'
189
+ expect(query).not_to be_successful
190
+ expect(query.errors[0].message).to eq("Invalid")
191
+ expect(query.errors[0].code).to eq("VALIDATION_ERROR")
192
+ end
152
193
  end
153
194
  ```
154
195
 
@@ -10,10 +10,10 @@ Gem::Specification.new do |spec|
10
10
 
11
11
  spec.summary = %q{Utiltities for building GraphQL APIs.}
12
12
  spec.description = %q{A set of modules and types for buildign GraphQL APIs.}
13
- spec.homepage = "https://github.com/promptworks/graphql-extras"
13
+ spec.homepage = "https://github.com/rzane/graphql-extras"
14
14
 
15
15
  spec.metadata["homepage_uri"] = spec.homepage
16
- spec.metadata["source_code_uri"] = "https://github.com/promptworks/graphql-extras"
16
+ spec.metadata["source_code_uri"] = "https://github.com/rzane/graphql-extras"
17
17
 
18
18
  # Specify which files should be added to the gem when it is released.
19
19
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -25,13 +25,12 @@ Gem::Specification.new do |spec|
25
25
  spec.require_paths = ["lib"]
26
26
 
27
27
  spec.add_dependency "activesupport", ">= 5.2"
28
- spec.add_dependency "graphql", "~> 1.9"
29
- spec.add_dependency "graphql-batch", "~> 0.4"
28
+ spec.add_dependency "graphql", "~> 1.12"
30
29
 
31
30
  spec.add_development_dependency "bundler", "~> 2.0"
32
- spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "rake", "~> 13.0"
33
32
  spec.add_development_dependency "rspec", "~> 3.0"
34
- spec.add_development_dependency "rspec-rails", "~> 3.8"
33
+ spec.add_development_dependency "rspec-rails", "~> 4.0"
35
34
  spec.add_development_dependency "actionpack", ">= 5.2"
36
35
  spec.add_development_dependency "activerecord", ">= 5.2"
37
36
  spec.add_development_dependency "sqlite3", "~> 1.4"
@@ -1,7 +1,7 @@
1
1
  require "graphql/extras/version"
2
2
  require "graphql/extras/types"
3
3
  require "graphql/extras/controller"
4
- require "graphql/extras/batch"
4
+ require "graphql/extras/preload"
5
5
 
6
6
  module GraphQL
7
7
  module Extras
@@ -0,0 +1,33 @@
1
+ module GraphQL
2
+ module Extras
3
+ class PreloadSource < GraphQL::Dataloader::Source
4
+ def initialize(preload)
5
+ @preload = preload
6
+ end
7
+
8
+ def fetch(records)
9
+ preloader = ActiveRecord::Associations::Preloader.new
10
+ preloader.preload(records, @preload)
11
+ records
12
+ end
13
+ end
14
+
15
+ module Preload
16
+ # @override
17
+ def initialize(*args, preload: nil, **opts, &block)
18
+ @preload = preload
19
+ super(*args, **opts, &block)
20
+ end
21
+
22
+ # @override
23
+ def resolve(object, args, context)
24
+ if @preload
25
+ loader = context.dataloader.with(PreloadSource, @preload)
26
+ loader.load(object.object)
27
+ end
28
+
29
+ super
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1 @@
1
+ require "graphql/extras/test/schema"
@@ -0,0 +1,59 @@
1
+ module GraphQL
2
+ module Extras
3
+ module Test
4
+ class Loader
5
+ FragmentNotFoundError = Class.new(StandardError)
6
+
7
+ attr_reader :fragments
8
+ attr_reader :operations
9
+
10
+ def initialize
11
+ @fragments = {}
12
+ @operations = {}
13
+ end
14
+
15
+ def load(path)
16
+ document = ::GraphQL.parse_file(path)
17
+ document.definitions.each do |node|
18
+ case node
19
+ when Nodes::FragmentDefinition
20
+ fragments[node.name] = node
21
+ when Nodes::OperationDefinition
22
+ operations[node.name] = node
23
+ end
24
+ end
25
+ end
26
+
27
+ def print(operation)
28
+ printer = ::GraphQL::Language::Printer.new
29
+ nodes = [operation, *resolve_fragments(operation)]
30
+ nodes.map { |node| printer.print(node) }.join("\n")
31
+ end
32
+
33
+ private
34
+
35
+ Nodes = ::GraphQL::Language::Nodes
36
+
37
+ # Recursively iterate through the node's fields and find
38
+ # resolve all of the fragment definitions that are needed.
39
+ def resolve_fragments(node)
40
+ node.selections.flat_map do |field|
41
+ case field
42
+ when Nodes::FragmentSpread
43
+ fragment = fetch_fragment!(field.name)
44
+ [fragment, *resolve_fragments(fragment)]
45
+ else
46
+ resolve_fragments(field)
47
+ end
48
+ end
49
+ end
50
+
51
+ def fetch_fragment!(name)
52
+ fragments.fetch(name) do
53
+ raise FragmentNotFoundError, "Fragment `#{name}` is not defined"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,37 @@
1
+ module GraphQL
2
+ module Extras
3
+ module Test
4
+ class Response
5
+ attr_reader :data
6
+ attr_reader :errors
7
+
8
+ def initialize(payload)
9
+ @data = payload["data"]
10
+ @errors = payload.fetch("errors", []).map do |error|
11
+ Error.new(error)
12
+ end
13
+ end
14
+
15
+ def successful?
16
+ errors.empty?
17
+ end
18
+
19
+ class Error
20
+ attr_reader :message
21
+ attr_reader :extensions
22
+ attr_reader :code
23
+ attr_reader :path
24
+ attr_reader :locations
25
+
26
+ def initialize(payload)
27
+ @message = payload["message"]
28
+ @path = payload["path"]
29
+ @locations = payload["locations"]
30
+ @extensions = payload["extensions"]
31
+ @code = payload.dig("extensions", "code")
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,57 @@
1
+ require "securerandom"
2
+ require "active_support/inflector"
3
+ require "active_support/core_ext/hash"
4
+ require "graphql/extras/test/loader"
5
+ require "graphql/extras/test/response"
6
+
7
+ module GraphQL
8
+ module Extras
9
+ module Test
10
+ class Schema
11
+ def self.configure(schema:, queries:)
12
+ loader = Loader.new
13
+
14
+ Dir.glob(queries) do |path|
15
+ loader.load(path)
16
+ end
17
+
18
+ loader.operations.each do |name, operation|
19
+ query = loader.print(operation)
20
+
21
+ define_method(name.underscore) do |variables = {}|
22
+ __execute(schema, query, variables)
23
+ end
24
+ end
25
+ end
26
+
27
+ def initialize(context = {})
28
+ @context = context
29
+ end
30
+
31
+ private
32
+
33
+ def __execute(schema, query, variables)
34
+ uploads = {}
35
+
36
+ variables = variables.deep_transform_keys do |key|
37
+ key.to_s.camelize(:lower)
38
+ end
39
+
40
+ variables = variables.deep_transform_values do |value|
41
+ if value.respond_to? :tempfile
42
+ id = SecureRandom.uuid
43
+ uploads[id] = value
44
+ id
45
+ else
46
+ value
47
+ end
48
+ end
49
+
50
+ context = @context.merge(uploads: uploads)
51
+ result = schema.execute(query, variables: variables, context: context)
52
+ Response.new(result.to_h)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module Extras
3
- VERSION = "0.2.5"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-extras
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ray Zane
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-16 00:00:00.000000000 Z
11
+ date: 2021-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,28 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.9'
33
+ version: '1.12'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.9'
41
- - !ruby/object:Gem::Dependency
42
- name: graphql-batch
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '0.4'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '0.4'
40
+ version: '1.12'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: bundler
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +58,14 @@ dependencies:
72
58
  requirements:
73
59
  - - "~>"
74
60
  - !ruby/object:Gem::Version
75
- version: '10.0'
61
+ version: '13.0'
76
62
  type: :development
77
63
  prerelease: false
78
64
  version_requirements: !ruby/object:Gem::Requirement
79
65
  requirements:
80
66
  - - "~>"
81
67
  - !ruby/object:Gem::Version
82
- version: '10.0'
68
+ version: '13.0'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: rspec
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +86,14 @@ dependencies:
100
86
  requirements:
101
87
  - - "~>"
102
88
  - !ruby/object:Gem::Version
103
- version: '3.8'
89
+ version: '4.0'
104
90
  type: :development
105
91
  prerelease: false
106
92
  version_requirements: !ruby/object:Gem::Requirement
107
93
  requirements:
108
94
  - - "~>"
109
95
  - !ruby/object:Gem::Version
110
- version: '3.8'
96
+ version: '4.0'
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: actionpack
113
99
  requirement: !ruby/object:Gem::Requirement
@@ -157,9 +143,10 @@ executables: []
157
143
  extensions: []
158
144
  extra_rdoc_files: []
159
145
  files:
146
+ - ".github/workflows/build.yml"
147
+ - ".github/workflows/publish.yml"
160
148
  - ".gitignore"
161
149
  - ".rspec"
162
- - ".travis.yml"
163
150
  - Gemfile
164
151
  - README.md
165
152
  - Rakefile
@@ -167,16 +154,19 @@ files:
167
154
  - bin/setup
168
155
  - graphql-extras.gemspec
169
156
  - lib/graphql/extras.rb
170
- - lib/graphql/extras/batch.rb
171
157
  - lib/graphql/extras/controller.rb
172
- - lib/graphql/extras/rspec.rb
158
+ - lib/graphql/extras/preload.rb
159
+ - lib/graphql/extras/test.rb
160
+ - lib/graphql/extras/test/loader.rb
161
+ - lib/graphql/extras/test/response.rb
162
+ - lib/graphql/extras/test/schema.rb
173
163
  - lib/graphql/extras/types.rb
174
164
  - lib/graphql/extras/version.rb
175
- homepage: https://github.com/promptworks/graphql-extras
165
+ homepage: https://github.com/rzane/graphql-extras
176
166
  licenses: []
177
167
  metadata:
178
- homepage_uri: https://github.com/promptworks/graphql-extras
179
- source_code_uri: https://github.com/promptworks/graphql-extras
168
+ homepage_uri: https://github.com/rzane/graphql-extras
169
+ source_code_uri: https://github.com/rzane/graphql-extras
180
170
  post_install_message:
181
171
  rdoc_options: []
182
172
  require_paths:
@@ -192,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
182
  - !ruby/object:Gem::Version
193
183
  version: '0'
194
184
  requirements: []
195
- rubygems_version: 3.1.2
185
+ rubygems_version: 3.0.3
196
186
  signing_key:
197
187
  specification_version: 4
198
188
  summary: Utiltities for building GraphQL APIs.
data/.travis.yml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.6.3
7
- before_install: gem install bundler -v 2.0.2
@@ -1,47 +0,0 @@
1
- require "graphql/batch"
2
-
3
- module GraphQL
4
- module Extras
5
- module Batch
6
- class AssociationLoader < GraphQL::Batch::Loader
7
- def initialize(name, preload: name)
8
- @name = name
9
- @preload = preload
10
- end
11
-
12
- def cache_key(record)
13
- record.object_id
14
- end
15
-
16
- def perform(records)
17
- preloader = ActiveRecord::Associations::Preloader.new
18
- preloader.preload(records, @preload)
19
-
20
- records.each do |record|
21
- fulfill(record, record.public_send(@name))
22
- end
23
- end
24
- end
25
-
26
- module Resolvers
27
- def self.included(base)
28
- base.extend ClassMethods
29
- end
30
-
31
- module ClassMethods
32
- def association(name)
33
- lambda do |record, _args, _ctx|
34
- loader = AssociationLoader.for(name)
35
- loader.load(record)
36
- end
37
- end
38
- end
39
-
40
- def association(record, name)
41
- loader = AssociationLoader.for(name)
42
- loader.load(record)
43
- end
44
- end
45
- end
46
- end
47
- end
@@ -1,133 +0,0 @@
1
- require "yaml"
2
- require "active_support/inflector"
3
- require "active_support/core_ext/hash"
4
-
5
- module GraphQL
6
- module Extras
7
- module RSpec
8
- class Queries
9
- def add(key, value)
10
- define_singleton_method(key) { value }
11
- end
12
- end
13
-
14
- class Schema
15
- def initialize(schema, context: {})
16
- @schema = schema
17
- @context = context
18
- end
19
-
20
- def execute(query, variables = {})
21
- variables = deep_camelize_keys(variables)
22
- variables, uploads = extract_uploads(variables)
23
- context = @context.merge(uploads: uploads)
24
-
25
- result = @schema.execute(query, variables: variables, context: context)
26
- result.to_h
27
- end
28
-
29
- private
30
-
31
- def extract_uploads(variables)
32
- uploads = {}
33
- variables = deep_transform_values(variables) { |value|
34
- if upload?(value)
35
- SecureRandom.hex.tap { |key| uploads.merge!(key => value) }
36
- else
37
- value
38
- end
39
- }
40
-
41
- [variables, uploads]
42
- end
43
-
44
- def deep_camelize_keys(variables)
45
- variables.deep_transform_keys { |key| key.to_s.camelize(:lower) }
46
- end
47
-
48
- def deep_transform_values(data, &block)
49
- case data
50
- when Array
51
- data.map { |v| deep_transform_values(v, &block) }
52
- when Hash
53
- data.transform_values { |v| deep_transform_values(v, &block) }
54
- else
55
- yield data
56
- end
57
- end
58
-
59
- def upload?(value)
60
- value.respond_to?(:tempfile) && value.respond_to?(:original_filename)
61
- end
62
- end
63
-
64
- class Parser
65
- include ::GraphQL::Language
66
-
67
- def initialize(document)
68
- @operations = document.definitions
69
- .grep(Nodes::OperationDefinition)
70
-
71
- @fragments = document.definitions
72
- .grep(Nodes::FragmentDefinition)
73
- .reduce({}) { |acc, f| acc.merge(f.name => f) }
74
- end
75
-
76
- def parse
77
- queries = Queries.new
78
- printer = Printer.new
79
-
80
- @operations.each do |op|
81
- nodes = [op, *find_fragments(op)]
82
- nodes = nodes.map { |node| printer.print(node) }
83
- queries.add op.name.underscore, nodes.join
84
- end
85
-
86
- queries
87
- end
88
-
89
- private
90
-
91
- def find_fragments(node)
92
- node.selections.flat_map do |field|
93
- if field.is_a? Nodes::FragmentSpread
94
- fragment = @fragments.fetch(field.name)
95
- [fragment, *find_fragments(fragment)]
96
- else
97
- find_fragments(field)
98
- end
99
- end
100
- end
101
- end
102
-
103
- def graphql_fixture(filename)
104
- root = ::RSpec.configuration.graphql_fixture_path
105
- file = File.join(root, filename)
106
- document = ::GraphQL.parse_file(file)
107
- parser = Parser.new(document)
108
- parser.parse
109
- end
110
-
111
- def use_schema(*args)
112
- Schema.new(*args)
113
- end
114
- end
115
- end
116
- end
117
-
118
- RSpec::Matchers.define :be_successful_query do
119
- match do |result|
120
- result['errors'].nil?
121
- end
122
-
123
- failure_message do |result|
124
- errors = result['errors'].map(&:deep_stringify_keys)
125
- message = "expected query to be successful, but encountered errors:\n"
126
- message + errors.to_yaml.lines.drop(1).join.indent(2)
127
- end
128
- end
129
-
130
- RSpec.configure do |config|
131
- config.add_setting :graphql_fixture_path, default: "spec/fixtures/graphql"
132
- config.include GraphQL::Extras::RSpec, type: :graphql
133
- end