graphql-extras 0.2.4 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 18eadb182c9a76519d6d2edeb5b0d4e1878fa09d
4
- data.tar.gz: 2c25c5003f3734e0c7c2c083cc47a76531eda759
2
+ SHA256:
3
+ metadata.gz: eb6a2324dfea95555ab147de0f4d79601603e21ca7dde008939795b12d40064c
4
+ data.tar.gz: ef735334a0b5be7ac0286c45886aa60d507482b73a95c0af89a2d1b5dfae27cc
5
5
  SHA512:
6
- metadata.gz: 7966e2f06aa836e34f0f42eecfdc1606f5a278f83d0c717f6001ab5c234424e6f50a3dd959ec57c477d9f74f9a35b25e25bbf38e47cda60216b70af3633b3231
7
- data.tar.gz: 291c570bd60ce89c6eb3beef5beab2a420967286c74ffbf19543f7623b3387feb70dc6871a3746463e01a2b611356d92e41d3fa3487755ec9061afe92db59b7b
6
+ metadata.gz: b412b43cd6d6c3865726770b1bd54d121e5a122fdafcd0f4f0e141edb6cd9e83ca13c47f73530025be3d16498144f97f3ddc40c02897049679bfebad8189ed56
7
+ data.tar.gz: '018ccec04e7e67b563e460fcbe7d1649a097087846b488985c34072004bcd2839162e4a3f3131c76718ea99a1d15633bc5c9f7ec3190cef65de203eb6c533a4b'
@@ -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::AssociationLoader](#graphqlextrasassociationloader)
18
+ - [GraphQL::Extras::Preload](#graphqlextraspreload)
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,30 +53,36 @@ class GraphqlController < ApplicationController
30
53
  end
31
54
  ```
32
55
 
33
- ### GraphQL::Extras::Batch::AssociationLoader
56
+ ### GraphQL::Extras::AssociationLoader
34
57
 
35
58
  This is a subclass of [`GraphQL::Batch::Loader`](https://github.com/Shopify/graphql-batch) that performs eager loading of Active Record associations.
36
59
 
37
60
  ```ruby
38
- loader = GraphQL::Extras::Batch::AssociationLoader.for(:blog)
61
+ loader = GraphQL::Extras::AssociationLoader.for(:blog)
39
62
  loader.load(Post.first)
40
63
  loader.load_many(Post.all)
41
64
  ```
42
65
 
43
- ### GraphQL::Extras::Batch::Resolvers
66
+ ### GraphQL::Extras::Preload
44
67
 
45
- This includes a set of convenience methods for query batching.
68
+ This allows you to preload associations before resolving fields.
46
69
 
47
70
  ```ruby
48
- class Post < GraphQL::Schema::Object
49
- include GraphQL::Extras::Batch::Resolver
71
+ class BaseField < GraphQL::Schema::Field
72
+ prepend GraphQL::Extras::Preload
73
+ end
74
+
75
+ class BaseObject < GraphQL::Schema::Object
76
+ field_class BaseField
77
+ end
50
78
 
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
79
+ class PostType < BaseObject
80
+ field :author, AuthorType, preload: :author, null: false
81
+ field :author_posts, [PostType], preload: {author: :posts}, null: false
82
+ field :depends_on_author, Integer, preload: :author, null: false
54
83
 
55
- def blog_title
56
- association(object, :blog).then(&:title)
84
+ def author_posts
85
+ object.author.posts
57
86
  end
58
87
  end
59
88
  ```
@@ -88,7 +117,7 @@ This scalar takes a `DateTime` and transmits it as a string, using ISO 8601 form
88
117
  field :created_at, DateTime, required: true
89
118
  ```
90
119
 
91
- *Note: This is just an alias for the `ISO8601DateTime` type that is included in the `graphql` gem.*
120
+ _Note: This is just an alias for the `ISO8601DateTime` type that is included in the `graphql` gem._
92
121
 
93
122
  #### Decimal
94
123
 
@@ -119,36 +148,44 @@ Take note of the correspondence between the value `"image"` and the additional H
119
148
 
120
149
  See [apollo-link-upload](https://github.com/rzane/apollo-link-upload) for the client-side implementation.
121
150
 
122
- ### RSpec integration
151
+ ### GraphQL::Extras::Test
123
152
 
124
- Add the following to your `rails_helper.rb` (or `spec_helper.rb`).
153
+ This module makes it really easy to test your schema.
154
+
155
+ First, create a test schema:
125
156
 
126
157
  ```ruby
127
- require "graphql/extras/rspec"
158
+ # spec/support/test_schema.rb
159
+ require "graphql/extras/test"
160
+
161
+ class TestSchema < GraphQL::Extras::Test::Schema
162
+ configure schema: Schema, queries: "spec/**/*.graphql"
163
+ end
128
164
  ```
129
165
 
130
166
  Now, you can run tests like so:
131
167
 
132
168
  ```ruby
169
+ require "support/test_schema"
170
+
133
171
  RSpec.describe "hello" do
134
172
  let(:context) { { name: "Ray" } }
135
- let(:schema) { use_schema(Schema, context: context) }
136
- let(:queries) { graphql_fixture("hello.graphql") }
173
+ let(:schema) { TestSchema.new(context) }
137
174
 
138
175
  it "allows easily executing queries" do
139
- result = schema.execute(queries.hello)
176
+ query = schema.hello
140
177
 
141
- expect(result).to be_successful_query
142
- expect(result['data']['hello']).to eq("world")
178
+ expect(query).to be_successful
179
+ expect(query.data["hello"]).to eq("world")
143
180
  end
144
- end
145
- ```
146
181
 
147
- The `graphql_fixture` method assumes that your queries live in `spec/fixtures/graphql`. You can change this assumption with the following configuration:
182
+ it "parses errors" do
183
+ query = schema.kaboom
148
184
 
149
- ```ruby
150
- RSpec.configure do |config|
151
- config.graphql_fixture_path = '/path/to/queries'
185
+ expect(query).not_to be_successful
186
+ expect(query.errors[0].message).to eq("Invalid")
187
+ expect(query.errors[0].code).to eq("VALIDATION_ERROR")
188
+ end
152
189
  end
153
190
  ```
154
191
 
@@ -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,13 @@ 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"
28
+ spec.add_dependency "graphql", "~> 1.12"
29
29
  spec.add_dependency "graphql-batch", "~> 0.4"
30
30
 
31
31
  spec.add_development_dependency "bundler", "~> 2.0"
32
- spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rake", "~> 13.0"
33
33
  spec.add_development_dependency "rspec", "~> 3.0"
34
- spec.add_development_dependency "rspec-rails", "~> 3.8"
34
+ spec.add_development_dependency "rspec-rails", "~> 4.0"
35
35
  spec.add_development_dependency "actionpack", ">= 5.2"
36
36
  spec.add_development_dependency "activerecord", ">= 5.2"
37
37
  spec.add_development_dependency "sqlite3", "~> 1.4"
@@ -1,7 +1,8 @@
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/association_loader"
5
+ require "graphql/extras/preload"
5
6
 
6
7
  module GraphQL
7
8
  module Extras
@@ -0,0 +1,24 @@
1
+ require "graphql/batch"
2
+
3
+ module GraphQL
4
+ module Extras
5
+ class AssociationLoader < GraphQL::Batch::Loader
6
+ def initialize(preload)
7
+ @preload = preload
8
+ end
9
+
10
+ def cache_key(record)
11
+ record.object_id
12
+ end
13
+
14
+ def perform(records)
15
+ preloader = ActiveRecord::Associations::Preloader.new
16
+ preloader.preload(records, @preload)
17
+
18
+ records.each do |record|
19
+ fulfill(record, nil) unless fulfilled?(record)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ require "graphql/extras/association_loader"
2
+
3
+ module GraphQL
4
+ module Extras
5
+ module Preload
6
+ # @override
7
+ def initialize(*args, preload: nil, **opts, &block)
8
+ @preload = preload
9
+ super(*args, **opts, &block)
10
+ end
11
+
12
+ # @override
13
+ def resolve(object, args, ctx)
14
+ return super unless @preload
15
+
16
+ loader = AssociationLoader.for(@preload)
17
+ loader.load(object.object).then do
18
+ super(object, args, ctx)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ 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.4"
3
+ VERSION = "0.3.2"
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.4
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ray Zane
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-10-04 00:00:00.000000000 Z
11
+ date: 2021-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,14 +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'
40
+ version: '1.12'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: graphql-batch
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '10.0'
75
+ version: '13.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '10.0'
82
+ version: '13.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rspec
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '3.8'
103
+ version: '4.0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '3.8'
110
+ version: '4.0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: actionpack
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -157,9 +157,10 @@ executables: []
157
157
  extensions: []
158
158
  extra_rdoc_files: []
159
159
  files:
160
+ - ".github/workflows/build.yml"
161
+ - ".github/workflows/publish.yml"
160
162
  - ".gitignore"
161
163
  - ".rspec"
162
- - ".travis.yml"
163
164
  - Gemfile
164
165
  - README.md
165
166
  - Rakefile
@@ -167,16 +168,20 @@ files:
167
168
  - bin/setup
168
169
  - graphql-extras.gemspec
169
170
  - lib/graphql/extras.rb
170
- - lib/graphql/extras/batch.rb
171
+ - lib/graphql/extras/association_loader.rb
171
172
  - lib/graphql/extras/controller.rb
172
- - lib/graphql/extras/rspec.rb
173
+ - lib/graphql/extras/preload.rb
174
+ - lib/graphql/extras/test.rb
175
+ - lib/graphql/extras/test/loader.rb
176
+ - lib/graphql/extras/test/response.rb
177
+ - lib/graphql/extras/test/schema.rb
173
178
  - lib/graphql/extras/types.rb
174
179
  - lib/graphql/extras/version.rb
175
- homepage: https://github.com/promptworks/graphql-extras
180
+ homepage: https://github.com/rzane/graphql-extras
176
181
  licenses: []
177
182
  metadata:
178
- homepage_uri: https://github.com/promptworks/graphql-extras
179
- source_code_uri: https://github.com/promptworks/graphql-extras
183
+ homepage_uri: https://github.com/rzane/graphql-extras
184
+ source_code_uri: https://github.com/rzane/graphql-extras
180
185
  post_install_message:
181
186
  rdoc_options: []
182
187
  require_paths:
@@ -192,8 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
197
  - !ruby/object:Gem::Version
193
198
  version: '0'
194
199
  requirements: []
195
- rubyforge_project:
196
- rubygems_version: 2.6.14
200
+ rubygems_version: 3.0.3
197
201
  signing_key:
198
202
  specification_version: 4
199
203
  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,103 +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 initialize(values)
10
- values.each do |key, value|
11
- define_singleton_method(key) { value }
12
- end
13
- end
14
- end
15
-
16
- class Schema
17
- def initialize(schema, context: {})
18
- @schema = schema
19
- @context = context
20
- end
21
-
22
- def execute(query, variables = {})
23
- variables = deep_camelize_keys(variables)
24
- variables, uploads = extract_uploads(variables)
25
- context = @context.merge(uploads: uploads)
26
-
27
- result = @schema.execute(query, variables: variables, context: context)
28
- result.to_h
29
- end
30
-
31
- private
32
-
33
- def extract_uploads(variables)
34
- uploads = {}
35
- variables = deep_transform_values(variables) { |value|
36
- if upload?(value)
37
- SecureRandom.hex.tap { |key| uploads.merge!(key => value) }
38
- else
39
- value
40
- end
41
- }
42
-
43
- [variables, uploads]
44
- end
45
-
46
- def deep_camelize_keys(variables)
47
- variables.deep_transform_keys { |key| key.to_s.camelize(:lower) }
48
- end
49
-
50
- def deep_transform_values(data, &block)
51
- case data
52
- when Array
53
- data.map { |v| deep_transform_values(v, &block) }
54
- when Hash
55
- data.transform_values { |v| deep_transform_values(v, &block) }
56
- else
57
- yield data
58
- end
59
- end
60
-
61
- def upload?(value)
62
- value.respond_to?(:tempfile) && value.respond_to?(:original_filename)
63
- end
64
- end
65
-
66
- def graphql_fixture(filename)
67
- root = ::RSpec.configuration.graphql_fixture_path
68
- contents = File.read(File.join(root, filename))
69
-
70
- parts = contents.split(/^(?=fragment|query|mutation|subscription)/)
71
- queries, fragments = parts.partition { |query| query !~ /^fragment/ }
72
-
73
- result = queries.reduce({}) { |acc, query|
74
- name = query.split(/\W+/).at(1).underscore.to_sym
75
- acc.merge(name => [*fragments, query].join)
76
- }
77
-
78
- Queries.new(result)
79
- end
80
-
81
- def use_schema(*args)
82
- Schema.new(*args)
83
- end
84
- end
85
- end
86
- end
87
-
88
- RSpec::Matchers.define :be_successful_query do
89
- match do |result|
90
- result['errors'].nil?
91
- end
92
-
93
- failure_message do |result|
94
- errors = result['errors'].map(&:deep_stringify_keys)
95
- message = "expected query to be successful, but encountered errors:\n"
96
- message + errors.to_yaml.lines.drop(1).join.indent(2)
97
- end
98
- end
99
-
100
- RSpec.configure do |config|
101
- config.add_setting :graphql_fixture_path, default: "spec/fixtures/graphql"
102
- config.include GraphQL::Extras::RSpec, type: :graphql
103
- end