graphiti_graphql 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +4 -4
- data/README.md +139 -15
- data/graphiti_graphql.gemspec +1 -0
- data/lib/graphiti_graphql.rb +47 -34
- data/lib/graphiti_graphql/engine.rb +2 -12
- data/lib/graphiti_graphql/federation.rb +12 -220
- data/lib/graphiti_graphql/federation/apollo_federation_override.rb +54 -0
- data/lib/graphiti_graphql/federation/federated_relationship.rb +26 -0
- data/lib/graphiti_graphql/federation/federated_resource.rb +26 -0
- data/lib/graphiti_graphql/federation/loaders/belongs_to.rb +22 -0
- data/lib/graphiti_graphql/federation/loaders/has_many.rb +33 -0
- data/lib/graphiti_graphql/federation/resource_dsl.rb +89 -0
- data/lib/graphiti_graphql/federation/schema_decorator.rb +150 -0
- data/lib/graphiti_graphql/graphiti_schema/resource.rb +0 -18
- data/lib/graphiti_graphql/graphiti_schema/wrapper.rb +0 -15
- data/lib/graphiti_graphql/runner.rb +15 -25
- data/lib/graphiti_graphql/schema.rb +50 -160
- data/lib/graphiti_graphql/util.rb +1 -1
- data/lib/graphiti_graphql/version.rb +1 -1
- metadata +23 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94c56621cb36c796bbefd0e8f626d90e359e2e8740b2cb443a8293e2f98b57df
|
4
|
+
data.tar.gz: dd1d065a140a292adab0f5d3c54e426dfcfa55b3691523f50ceb947fa752fdbd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94886a1ebbed6fee376239b1a10c197c3891cd280aad29973a7a1ce4d6f769e9c1fc3949bb4613dbe98c44d1f584a1b2863955c605798525e24ee1766b136891
|
7
|
+
data.tar.gz: d0ca0298e0e77da84361a1748a2b36457569910d43aed602a94cfd6eb8869c32c8a0db611bca68dbf64382fe0c5cd8dddb8956c105af9ae7819c4e92cce03adc
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../graphiti
|
3
3
|
specs:
|
4
|
-
graphiti (1.2.
|
4
|
+
graphiti (1.2.32)
|
5
5
|
activesupport (>= 4.1)
|
6
6
|
concurrent-ruby (~> 1.0)
|
7
7
|
dry-types (>= 0.15.0, < 2.0)
|
@@ -12,7 +12,7 @@ PATH
|
|
12
12
|
PATH
|
13
13
|
remote: .
|
14
14
|
specs:
|
15
|
-
graphiti_graphql (0.1.
|
15
|
+
graphiti_graphql (0.1.1)
|
16
16
|
activesupport (>= 4.1)
|
17
17
|
graphql (~> 1.12)
|
18
18
|
|
@@ -35,7 +35,7 @@ GEM
|
|
35
35
|
coderay (1.1.3)
|
36
36
|
concurrent-ruby (1.1.8)
|
37
37
|
diff-lcs (1.4.4)
|
38
|
-
dry-configurable (0.12.
|
38
|
+
dry-configurable (0.12.1)
|
39
39
|
concurrent-ruby (~> 1.0)
|
40
40
|
dry-core (~> 0.5, >= 0.5.0)
|
41
41
|
dry-container (0.7.2)
|
@@ -47,7 +47,7 @@ GEM
|
|
47
47
|
dry-logic (1.1.0)
|
48
48
|
concurrent-ruby (~> 1.0)
|
49
49
|
dry-core (~> 0.5, >= 0.5)
|
50
|
-
dry-types (1.5.
|
50
|
+
dry-types (1.5.1)
|
51
51
|
concurrent-ruby (~> 1.0)
|
52
52
|
dry-container (~> 0.3)
|
53
53
|
dry-core (~> 0.5, >= 0.5)
|
data/README.md
CHANGED
@@ -1,35 +1,159 @@
|
|
1
1
|
# GraphitiGraphql
|
2
2
|
|
3
|
-
|
3
|
+
GraphQL (and Apollo Federation) support for Graphiti. Serve traditional Rails JSON, JSON:API or GraphQL with the same codebase.
|
4
4
|
|
5
|
-
|
5
|
+
Currently read-only, but you can add your own Mutations [manually](#blending-with-graphql-ruby).
|
6
6
|
|
7
|
-
##
|
7
|
+
## Setup
|
8
8
|
|
9
|
-
Add
|
9
|
+
Add to your `Gemfile`:
|
10
|
+
|
11
|
+
```rb
|
12
|
+
gem 'graphiti', ">= 1.2.32"
|
13
|
+
gem "graphiti_graphql"
|
14
|
+
```
|
15
|
+
|
16
|
+
Mount the engine:
|
10
17
|
|
11
18
|
```ruby
|
12
|
-
|
19
|
+
# config/routes.rb
|
20
|
+
Rails.application.routes.draw do
|
21
|
+
scope path: ApplicationResource.endpoint_namespace, defaults: { format: :jsonapi } do
|
22
|
+
# ... normal graphiti stuff ...
|
23
|
+
|
24
|
+
mount GraphitiGraphQL::Engine, at: "/gql"
|
25
|
+
end
|
26
|
+
end
|
13
27
|
```
|
14
28
|
|
15
|
-
|
29
|
+
For a default Graphiti app, you can now serve GraphQL by POSTing to `/api/v1/gql`.
|
30
|
+
|
31
|
+
That's it 🎉!
|
16
32
|
|
17
|
-
|
33
|
+
#### GraphiQL
|
34
|
+
|
35
|
+
You can add the GraphiQL editor to the project via [graphiql-rails](https://github.com/rmosolgo/graphiql-rails) as normal, but to save you the time here are the steps to make it work when Rails is running in API-only mode:
|
36
|
+
|
37
|
+
Add to the Gemfile:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
gem "graphiql-rails"
|
41
|
+
gem 'sprockets', '~> 3' # https://github.com/rmosolgo/graphiql-rails/issues/53
|
42
|
+
```
|
18
43
|
|
19
|
-
|
44
|
+
And then in `config/application.rb`:
|
20
45
|
|
21
|
-
|
46
|
+
```ruby
|
47
|
+
# *Uncomment* this line!
|
48
|
+
# require "sprockets/railtie"
|
49
|
+
```
|
22
50
|
|
23
51
|
## Usage
|
24
52
|
|
25
|
-
|
53
|
+
#### Blending with graphql-ruby
|
54
|
+
|
55
|
+
Define your Schema and Type classes as normal. Then in an initializer:
|
26
56
|
|
27
|
-
|
57
|
+
```ruby
|
58
|
+
# config/initializers/graphiti.rb
|
59
|
+
GraphitiGraphQL.schema_class = MySchema
|
60
|
+
```
|
28
61
|
|
29
|
-
|
62
|
+
Any pre-existing GraphQL endpoint will continue working as normal. But the GQL endpoint you mounted in `config/routes.rb` will now serve BOTH your low-level `graphql-ruby` schema AND your Graphiti-specific schema. Note these cannot (currently) be served side-by-side on under `query` within the *same request*.
|
30
63
|
|
31
|
-
|
64
|
+
By default the GraphQL context will be `Graphiti.context[:object]`, which is the controller being called. You might want to customize this so your existing graphql-ruby code continues to expect the same context:
|
32
65
|
|
33
|
-
|
66
|
+
```ruby
|
67
|
+
GraphitiGraphQL.define_context do |controller|
|
68
|
+
{ current_user: controller.current_user }
|
69
|
+
end
|
70
|
+
```
|
34
71
|
|
35
|
-
|
72
|
+
#### Adding Federation Support
|
73
|
+
|
74
|
+
Add to the Gemfile
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
gem "apollo-federation"
|
78
|
+
gem "graphql-batch"
|
79
|
+
```
|
80
|
+
|
81
|
+
And change the way we require `graphiti_graphql`:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
gem "graphiti_graphql", require: "graphiti_graphql/federation"
|
85
|
+
```
|
86
|
+
|
87
|
+
To create a federated relationship:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
# PositionResource
|
91
|
+
federated_belongs_to :employee
|
92
|
+
```
|
93
|
+
|
94
|
+
Or pass `type` and/or `foreign_key` to customize:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
# type here is the GraphQL Type
|
98
|
+
federated_belongs_to :employee, type: "MyEmployee", foreign_key: :emp_id
|
99
|
+
```
|
100
|
+
|
101
|
+
For `has_many` it's a slightly different syntax because we're adding the relationship to the ***remote*** type:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
federated_type("Employee").has_many :positions # foreign_key: optional
|
105
|
+
```
|
106
|
+
|
107
|
+
Finally, `has_many` accepts the traditional `params` block that works as normal:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
federated_type("Employee").has_many :positions do
|
111
|
+
params do |hash|
|
112
|
+
hash[:filter][:active] = true
|
113
|
+
hash[:sort] = "-title"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
Remember that any time you make a change that affects the schema, you will have to bounce your federation gateway. This is how Apollo Federation works when not in "managed" mode and is unrelated to `graphiti_graphql`.
|
119
|
+
|
120
|
+
## Configuration
|
121
|
+
|
122
|
+
#### Entrypoints
|
123
|
+
|
124
|
+
By default all Graphiti resources will expose their `index` and `show` functionality. IOW `EmployeeResource` now serves a list at `Query#employees` and a single employee at `Query#employee(id: 123)`. To limit the entrypoints:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
GraphitiGraphQL::Schema.entrypoints = [
|
128
|
+
EmployeeResource
|
129
|
+
]
|
130
|
+
```
|
131
|
+
|
132
|
+
#### Schema Reloading
|
133
|
+
|
134
|
+
You may want to automatically regenerate the GQL schema when when Rails reloads your classes, or you may not want to pay that performance penalty. To turn off the automatic reloading:
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
# config/initializers/graphiti.rb
|
138
|
+
GraphitiGraphQL.config.schema_reloading = false
|
139
|
+
```
|
140
|
+
|
141
|
+
#### `.graphql_entrypoint`
|
142
|
+
|
143
|
+
If the field you want on `Query` can't be inferred from the class name:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
class EmployeeResource < ApplicationResource
|
147
|
+
self.graphql_entrypoint = :workers
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
You can now
|
152
|
+
|
153
|
+
```
|
154
|
+
query {
|
155
|
+
workers {
|
156
|
+
firstName
|
157
|
+
}
|
158
|
+
}
|
159
|
+
```
|
data/graphiti_graphql.gemspec
CHANGED
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
+
spec.add_dependency "graphiti", ">= 1.2.33"
|
30
31
|
spec.add_dependency "activesupport", ">= 4.1"
|
31
32
|
spec.add_dependency "graphql", "~> 1.12"
|
32
33
|
|
data/lib/graphiti_graphql.rb
CHANGED
@@ -11,38 +11,58 @@ require "graphiti_graphql/schema"
|
|
11
11
|
require "graphiti_graphql/runner"
|
12
12
|
require "graphiti_graphql/util"
|
13
13
|
|
14
|
-
|
15
|
-
class
|
16
|
-
|
17
|
-
end
|
14
|
+
module GraphitiGraphQL
|
15
|
+
class Configuration
|
16
|
+
attr_accessor :schema_reloading
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
runner.execute(query, variables, graphql_schema.schema)
|
23
|
-
end
|
18
|
+
def initialize
|
19
|
+
self.schema_reloading = true
|
20
|
+
end
|
24
21
|
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
def define_context(&blk)
|
23
|
+
@define_context = blk
|
24
|
+
end
|
28
25
|
|
29
|
-
|
30
|
-
|
26
|
+
def get_context
|
27
|
+
obj = Graphiti.context[:object]
|
28
|
+
if @define_context
|
29
|
+
@define_context.call(obj)
|
30
|
+
else
|
31
|
+
{object: obj}
|
32
|
+
end
|
33
|
+
end
|
31
34
|
end
|
32
35
|
|
33
|
-
|
34
|
-
|
36
|
+
module Runnable
|
37
|
+
def gql(query, variables)
|
38
|
+
runner = ::GraphitiGraphQL::Runner.new
|
39
|
+
runner.execute(query, variables, GraphitiGraphQL.schemas.graphql)
|
40
|
+
end
|
35
41
|
end
|
36
|
-
end
|
37
42
|
|
38
|
-
|
39
|
-
|
43
|
+
class SchemaProxy
|
44
|
+
def graphql
|
45
|
+
generated.schema
|
46
|
+
end
|
40
47
|
|
41
|
-
|
42
|
-
|
48
|
+
def graphiti
|
49
|
+
generated.graphiti_schema
|
50
|
+
end
|
43
51
|
|
44
|
-
def
|
45
|
-
|
52
|
+
def generated
|
53
|
+
@generated ||= GraphitiGraphQL::Schema.generate
|
54
|
+
end
|
55
|
+
|
56
|
+
def generate!(entrypoint_resources = nil)
|
57
|
+
@generated = GraphitiGraphQL::Schema.generate(entrypoint_resources)
|
58
|
+
end
|
59
|
+
|
60
|
+
def generated?
|
61
|
+
!!@generated
|
62
|
+
end
|
63
|
+
|
64
|
+
def clear!
|
65
|
+
@generated = nil
|
46
66
|
end
|
47
67
|
end
|
48
68
|
|
@@ -54,20 +74,13 @@ module GraphitiGraphQL
|
|
54
74
|
@config ||= Configuration.new
|
55
75
|
end
|
56
76
|
|
57
|
-
def self.
|
58
|
-
@
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.get_context
|
62
|
-
obj = Graphiti.context[:object]
|
63
|
-
if @define_context
|
64
|
-
@define_context.call(obj)
|
65
|
-
else
|
66
|
-
{object: obj}
|
67
|
-
end
|
77
|
+
def self.schemas
|
78
|
+
@schemas ||= SchemaProxy.new
|
68
79
|
end
|
69
80
|
end
|
70
81
|
|
82
|
+
Graphiti.extend(GraphitiGraphQL::Runnable)
|
83
|
+
|
71
84
|
if defined?(::Rails)
|
72
85
|
require "graphiti_graphql/engine"
|
73
86
|
end
|
@@ -48,16 +48,6 @@ module GraphitiGraphQL
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
# We store a list of all federated relationships
|
52
|
-
# Clear that list when classes are cleared so we don't keep appending
|
53
|
-
initializer "graphiti_graphql.clear_federation" do |app|
|
54
|
-
if defined?(GraphitiGraphQL::Federation)
|
55
|
-
GraphitiGraphQL::Engine.reloader_class.after_class_unload do
|
56
|
-
GraphitiGraphQL::Federation.clear!
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
51
|
initializer "graphiti_graphql.schema_reloading" do |app|
|
62
52
|
# Only reload the schema if we ask for it
|
63
53
|
# Some may want to avoid the performance penalty
|
@@ -66,8 +56,8 @@ module GraphitiGraphQL
|
|
66
56
|
# We want to reload the schema when classes change
|
67
57
|
# But this way, you only pay the cost (time) when the GraphQL endpoint
|
68
58
|
# is actually hit
|
69
|
-
if
|
70
|
-
|
59
|
+
if GraphitiGraphQL.schemas.generated?
|
60
|
+
GraphitiGraphQL.schemas.clear!
|
71
61
|
end
|
72
62
|
end
|
73
63
|
end
|
@@ -12,27 +12,28 @@ end
|
|
12
12
|
|
13
13
|
# We don't want to add these as dependencies,
|
14
14
|
# but do need to check things don't break
|
15
|
-
if Gem::Version.new(ApolloFederation::VERSION) >= Gem::Version.new(
|
15
|
+
if Gem::Version.new(ApolloFederation::VERSION) >= Gem::Version.new("2.0.0")
|
16
16
|
raise "graphiti_graphql federation is incompatible with apollo-federation >= 2"
|
17
17
|
end
|
18
18
|
|
19
|
-
if Gem::Version.new(GraphQL::Batch::VERSION) >= Gem::Version.new(
|
19
|
+
if Gem::Version.new(GraphQL::Batch::VERSION) >= Gem::Version.new("1.0.0")
|
20
20
|
raise "graphiti_graphql federation is incompatible with graphql-batch >= 1"
|
21
21
|
end
|
22
22
|
|
23
23
|
require "graphiti_graphql"
|
24
|
+
require "graphiti_graphql/federation/loaders/has_many"
|
25
|
+
require "graphiti_graphql/federation/loaders/belongs_to"
|
26
|
+
require "graphiti_graphql/federation/federated_resource"
|
27
|
+
require "graphiti_graphql/federation/federated_relationship"
|
28
|
+
require "graphiti_graphql/federation/resource_dsl"
|
29
|
+
require "graphiti_graphql/federation/apollo_federation_override"
|
30
|
+
require "graphiti_graphql/federation/schema_decorator"
|
24
31
|
|
25
32
|
module GraphitiGraphQL
|
26
33
|
module Federation
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.clear!
|
33
|
-
@external_resources = {}
|
34
|
-
end
|
35
|
-
|
34
|
+
# * Extend Graphiti::Resource with federated_* macros
|
35
|
+
# * Add apollo-federation modules to graphql-ruby base types
|
36
|
+
# * Mark federation = true for checks down the line
|
36
37
|
def self.setup!
|
37
38
|
Graphiti::Resource.send(:include, ResourceDSL)
|
38
39
|
schema = GraphitiGraphQL::Schema
|
@@ -50,214 +51,5 @@ module GraphitiGraphQL
|
|
50
51
|
schema.base_interface.field_class(schema.base_field)
|
51
52
|
GraphitiGraphQL::Schema.federation = true
|
52
53
|
end
|
53
|
-
|
54
|
-
class HasManyLoader < GraphQL::Batch::Loader
|
55
|
-
def initialize(resource_class, params, foreign_key)
|
56
|
-
@resource_class = resource_class
|
57
|
-
@params = params
|
58
|
-
@foreign_key = foreign_key
|
59
|
-
end
|
60
|
-
|
61
|
-
def perform(ids)
|
62
|
-
@params[:filter] ||= {}
|
63
|
-
@params[:filter].merge!(@foreign_key => { eq: ids.join(",") })
|
64
|
-
|
65
|
-
if ids.length > 1 && @params[:page]
|
66
|
-
raise Graphiti::Errors::UnsupportedPagination
|
67
|
-
elsif !@params[:page]
|
68
|
-
@params[:page] = { size: 999 }
|
69
|
-
end
|
70
|
-
|
71
|
-
Util.with_gql_context do
|
72
|
-
records = @resource_class.all(@params).as_json[:data]
|
73
|
-
fk = ->(record) { record[@foreign_key].to_s }
|
74
|
-
map = records.group_by(&fk)
|
75
|
-
ids.each do |id|
|
76
|
-
fulfill(id, (map[id] || []))
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
class BelongsToLoader < GraphQL::Batch::Loader
|
83
|
-
def initialize(resource_class, fields)
|
84
|
-
@resource_class = resource_class
|
85
|
-
@fields = fields
|
86
|
-
end
|
87
|
-
|
88
|
-
def perform(ids)
|
89
|
-
Util.with_gql_context do
|
90
|
-
params = { filter: { id: { eq: ids.join(",") } } }
|
91
|
-
params[:fields] = { @resource_class.type => @fields.join(",") }
|
92
|
-
records = @resource_class.all(params).as_json[:data]
|
93
|
-
pk = ->(record) { record[:id].to_s }
|
94
|
-
map = records.index_by(&pk)
|
95
|
-
ids.each { |id| fulfill(id, map[id]) }
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
class ExternalRelationship
|
101
|
-
attr_reader :name, :local_resource_class, :foreign_key
|
102
|
-
|
103
|
-
def initialize(kind, name, local_resource_class, foreign_key)
|
104
|
-
@kind = kind
|
105
|
-
@name = name
|
106
|
-
@local_resource_class = local_resource_class
|
107
|
-
@foreign_key = foreign_key
|
108
|
-
end
|
109
|
-
|
110
|
-
def has_many?
|
111
|
-
@kind == :has_many
|
112
|
-
end
|
113
|
-
|
114
|
-
def belongs_to?
|
115
|
-
@kind == :belongs_to
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
class ExternalResource
|
120
|
-
attr_reader :type_name, :relationships
|
121
|
-
|
122
|
-
def initialize(type_name)
|
123
|
-
@type_name = type_name
|
124
|
-
@relationships = {}
|
125
|
-
end
|
126
|
-
|
127
|
-
def add_relationship(
|
128
|
-
kind,
|
129
|
-
name,
|
130
|
-
local_resource_class,
|
131
|
-
foreign_key
|
132
|
-
)
|
133
|
-
@relationships[name] = ExternalRelationship
|
134
|
-
.new(kind, name, local_resource_class, foreign_key)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
class TypeProxy
|
139
|
-
def initialize(caller, type_name)
|
140
|
-
@caller = caller
|
141
|
-
@type_name = type_name
|
142
|
-
end
|
143
|
-
|
144
|
-
def has_many(relationship_name, foreign_key: nil)
|
145
|
-
@caller.federated_has_many relationship_name,
|
146
|
-
type: @type_name,
|
147
|
-
foreign_key: foreign_key
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
module ResourceDSL
|
152
|
-
extend ActiveSupport::Concern
|
153
|
-
|
154
|
-
class_methods do
|
155
|
-
def federated_type(type_name)
|
156
|
-
TypeProxy.new(self, type_name)
|
157
|
-
end
|
158
|
-
|
159
|
-
# TODO: raise error if belongs_to doesn't have corresponding filter (on schema gen)
|
160
|
-
# TODO: hang these on the resource classes themselves
|
161
|
-
def federated_has_many(name, type:, foreign_key: nil)
|
162
|
-
foreign_key ||= :"#{type.underscore}_id"
|
163
|
-
resource = GraphitiGraphQL::Federation.external_resources[type] ||=
|
164
|
-
ExternalResource.new(type)
|
165
|
-
resource.add_relationship(:has_many, name, self, foreign_key)
|
166
|
-
|
167
|
-
attribute = attributes.find do |name, config|
|
168
|
-
name.to_sym == foreign_key && !!config[:readable] && !!config[:filterable]
|
169
|
-
end
|
170
|
-
has_filter = filters.key?(foreign_key)
|
171
|
-
if !attribute && !has_filter
|
172
|
-
attribute foreign_key, :integer,
|
173
|
-
only: [:readable, :filterable],
|
174
|
-
schema: false,
|
175
|
-
readable: :gql?,
|
176
|
-
filterable: :gql?
|
177
|
-
elsif has_filter && !attribute
|
178
|
-
prior = filters[foreign_key]
|
179
|
-
attribute foreign_key, prior[:type],
|
180
|
-
only: [:readable, :filterable],
|
181
|
-
schema: false,
|
182
|
-
readable: :gql?
|
183
|
-
filters[foreign_key] = prior
|
184
|
-
elsif attribute && !has_filter
|
185
|
-
filter foreign_key, attribute[:type]
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
def federated_belongs_to(name, type: nil, foreign_key: nil)
|
190
|
-
type ||= name.to_s.camelize
|
191
|
-
foreign_key ||= :"#{name.to_s.underscore}_id"
|
192
|
-
resource = GraphitiGraphQL::Federation.external_resources[type] ||=
|
193
|
-
ExternalResource.new(type)
|
194
|
-
resource.add_relationship(:belongs_to, name, self, foreign_key)
|
195
|
-
|
196
|
-
attribute name, :hash, readable: :gql?, only: [:readable], schema: false do
|
197
|
-
fk = if prc = self.class.attribute_blocks[foreign_key]
|
198
|
-
instance_eval(&prc)
|
199
|
-
else
|
200
|
-
@object.send(foreign_key)
|
201
|
-
end
|
202
|
-
{
|
203
|
-
__typename: type,
|
204
|
-
id: fk.to_s
|
205
|
-
}
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def gql?
|
211
|
-
Graphiti.context[:graphql]
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
# Hacky sack!
|
218
|
-
# All we're doing here is adding extras: [:lookahead] to the _entities field
|
219
|
-
# And passing to to the .resolve_reference method when arity is 3
|
220
|
-
# This way we can request only fields the user wants when resolving the reference
|
221
|
-
# Important because we blow up when a field is guarded, and the guard fails
|
222
|
-
ApolloFederation::EntitiesField::ClassMethods.module_eval do
|
223
|
-
alias_method :define_entities_field_without_override, :define_entities_field
|
224
|
-
def define_entities_field(*args)
|
225
|
-
result = define_entities_field_without_override(*args)
|
226
|
-
extras = fields["_entities"].extras
|
227
|
-
extras |= [:lookahead]
|
228
|
-
fields["_entities"].instance_variable_set(:@extras, extras)
|
229
|
-
result
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
module EntitiesFieldOverride
|
234
|
-
def _entities(representations:, lookahead:) # accept the lookahead as argument
|
235
|
-
representations.map do |reference|
|
236
|
-
typename = reference[:__typename]
|
237
|
-
type = context.warden.get_type(typename)
|
238
|
-
if type.nil? || type.kind != GraphQL::TypeKinds::OBJECT
|
239
|
-
raise "The _entities resolver tried to load an entity for type \"#{typename}\"," \
|
240
|
-
' but no object type of that name was found in the schema'
|
241
|
-
end
|
242
|
-
|
243
|
-
type_class = type.is_a?(GraphQL::ObjectType) ? type.metadata[:type_class] : type
|
244
|
-
if type_class.respond_to?(:resolve_reference)
|
245
|
-
meth = type_class.method(:resolve_reference)
|
246
|
-
# ** THIS IS OUR EDIT **
|
247
|
-
result = if meth.arity == 3
|
248
|
-
type_class.resolve_reference(reference, context, lookahead)
|
249
|
-
else
|
250
|
-
type_class.resolve_reference(reference, context)
|
251
|
-
end
|
252
|
-
else
|
253
|
-
result = reference
|
254
|
-
end
|
255
|
-
|
256
|
-
context.schema.after_lazy(result) do |resolved_value|
|
257
|
-
context[resolved_value] = type
|
258
|
-
resolved_value
|
259
|
-
end
|
260
|
-
end
|
261
54
|
end
|
262
55
|
end
|
263
|
-
ApolloFederation::EntitiesField.send :prepend, EntitiesFieldOverride
|