graphiti_gql 0.1.0 → 0.2.0
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 +4 -4
- data/README.md +88 -1
- data/lib/graphiti_gql/engine.rb +2 -2
- data/lib/graphiti_gql/errors.rb +16 -0
- data/lib/graphiti_gql/graphiti_hax.rb +199 -52
- data/lib/graphiti_gql/schema/fields/attribute.rb +6 -4
- data/lib/graphiti_gql/schema/list_arguments.rb +10 -7
- data/lib/graphiti_gql/schema.rb +17 -1
- data/lib/graphiti_gql/spec_helper.rb +150 -0
- data/lib/graphiti_gql/version.rb +1 -1
- data/lib/graphiti_gql.rb +12 -0
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a19fd68dc698b8a7284fe80686da9f50ee137365508241cd374e32aa6c505aa
|
4
|
+
data.tar.gz: d509f137452b90e94986c46267f8728160021de305e599ba47512af9366fe109
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22f8643e779d272d806d53179e5f83e6ca6157fc8c261e03ea3232fdf685faa35ad50c70bd3f5f0f445b8bd4578e616b9a538c9205e330a301a30f370b2c612a
|
7
|
+
data.tar.gz: ec6e08b56162dc2bb077bfd3cfc5fb5dcf8edcdc3680d940347eb33949f8530967e6d828e0b914f0920a406bad4807d83c643981f9ab0136dd7a3e9e422e068a
|
data/README.md
CHANGED
@@ -1,3 +1,90 @@
|
|
1
1
|
# GraphitiGql
|
2
2
|
|
3
|
-
|
3
|
+
GraphQL bindings for [Graphiti](www.graphiti.dev).
|
4
|
+
|
5
|
+
Write code like this:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class EmployeeResource < ApplicationResource
|
9
|
+
attribute :first_name, :string
|
10
|
+
attribute :age, :integer
|
11
|
+
|
12
|
+
has_many :positions
|
13
|
+
end
|
14
|
+
```
|
15
|
+
|
16
|
+
Get an API like this:
|
17
|
+
|
18
|
+
```gql
|
19
|
+
query {
|
20
|
+
employees(
|
21
|
+
filter: { firstName: { match: "arha" } },
|
22
|
+
sort: [{ att: age, dir: desc }],
|
23
|
+
first: 10,
|
24
|
+
after: "abc123"
|
25
|
+
) {
|
26
|
+
edges {
|
27
|
+
node {
|
28
|
+
id
|
29
|
+
firstName
|
30
|
+
age
|
31
|
+
positions {
|
32
|
+
nodes {
|
33
|
+
title
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
cursor
|
38
|
+
}
|
39
|
+
stats {
|
40
|
+
total {
|
41
|
+
count
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
```
|
47
|
+
|
48
|
+
### Getting Started
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# Gemfile
|
52
|
+
gem 'graphiti'
|
53
|
+
gem "graphiti-rails"
|
54
|
+
gem 'graphiti_gql'
|
55
|
+
```
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# config/routes.rb
|
59
|
+
|
60
|
+
Rails.application.routes.draw do
|
61
|
+
scope path: ApplicationResource.endpoint_namespace do
|
62
|
+
mount GraphitiGql::Engine, at: "/gql"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
Write your Graphiti code as normal, omit controllers.
|
68
|
+
|
69
|
+
### How does it work?
|
70
|
+
|
71
|
+
This autogenerates `graphql-ruby` code by introspecting Graphiti Resources. Something like this happens under-the-hood:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
field :employees, [EmployeeType], null: false do
|
75
|
+
argument :filter, EmployeeFilter, required: false
|
76
|
+
# ... etc ...
|
77
|
+
end
|
78
|
+
|
79
|
+
def employees(**arguments)
|
80
|
+
EmployeeResource.all(**arguments).to_a
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
In practice it's more complicated, but this is the basic premise - use Graphiti resources to handle query and persistence operations; autogenerate `graphql-ruby` code to expose those Resources as an API. This means we play nicely with e.g. telemetry and error-handling libraries because it's all `graphql-ruby` under-the-hood...except for actually **performing** the operations, which is really more a Ruby thing than a GraphQL thing.
|
85
|
+
|
86
|
+
### Caveats
|
87
|
+
|
88
|
+
This rethinks the responsibilities of Graphiti, coupling the execution cycle to `graphql-ruby`. We do this so we can play nicely with other gems in the GQL ecosystem, and saves on development time by offloading responsibilities. The downside is we can no longer run a `JSON:API` with the same codebase, and certain documentation may be out of date.
|
89
|
+
|
90
|
+
Longer-term, we should rip out only the parts of Graphiti we really need and redocument.
|
data/lib/graphiti_gql/engine.rb
CHANGED
@@ -2,6 +2,7 @@ module GraphitiGql
|
|
2
2
|
class Engine < ::Rails::Engine
|
3
3
|
isolate_namespace GraphitiGql
|
4
4
|
|
5
|
+
# TODO improvable?
|
5
6
|
config.after_initialize do
|
6
7
|
# initializer "graphiti_gql.generate_schema" do
|
7
8
|
Dir.glob("#{Rails.root}/app/resources/**/*").each { |f| require(f) }
|
@@ -10,11 +11,10 @@ module GraphitiGql
|
|
10
11
|
|
11
12
|
initializer "graphiti_gql.define_controller" do
|
12
13
|
require "#{Rails.root}/app/controllers/application_controller"
|
13
|
-
app_controller = ::ApplicationController
|
14
|
+
app_controller = GraphitiGql.config.application_controller || ::ApplicationController
|
14
15
|
|
15
16
|
# rubocop:disable Lint/ConstantDefinitionInBlock(Standard)
|
16
17
|
class GraphitiGql::ExecutionController < app_controller
|
17
|
-
# register_exception Graphiti::Errors::UnreadableAttribute, message: true
|
18
18
|
def execute
|
19
19
|
params = request.params # avoid strong_parameters
|
20
20
|
variables = params[:variables] || {}
|
data/lib/graphiti_gql/errors.rb
CHANGED
@@ -17,5 +17,21 @@ module GraphitiGql
|
|
17
17
|
"You are not authorized to read field #{@field}"
|
18
18
|
end
|
19
19
|
end
|
20
|
+
|
21
|
+
class NullFilter < Base
|
22
|
+
def initialize(name)
|
23
|
+
@name = name
|
24
|
+
end
|
25
|
+
|
26
|
+
def message
|
27
|
+
"Filter '#{@name}' does not support null"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class UnsupportedLast < Base
|
32
|
+
def message
|
33
|
+
"We do not currently support combining 'last' with 'before' or 'after'"
|
34
|
+
end
|
35
|
+
end
|
20
36
|
end
|
21
37
|
end
|
@@ -1,71 +1,218 @@
|
|
1
1
|
# These should be in Graphiti itself, but can't do it quite yet b/c GQL coupling.
|
2
2
|
# Ideally we eventually rip out the parts of Graphiti we need and roll this into
|
3
3
|
# that effort.
|
4
|
-
module
|
5
|
-
|
4
|
+
module GraphitiGql
|
5
|
+
module ResourceExtras
|
6
|
+
extend ActiveSupport::Concern
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
included do
|
9
|
+
class << self
|
10
|
+
attr_accessor :graphql_name
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
def attribute(*args)
|
16
|
+
super(*args).tap do
|
17
|
+
opts = args.extract_options!
|
18
|
+
att = config[:attributes][args[0]]
|
19
|
+
att[:deprecation_reason] = opts[:deprecation_reason]
|
20
|
+
att[:null] = opts.key?(:null) ? opts[:null] : args[0] != :id
|
21
|
+
att[:name] = args.first # for easier lookup
|
22
|
+
end
|
23
|
+
end
|
10
24
|
end
|
11
25
|
end
|
26
|
+
Graphiti::Resource.send(:include, ResourceExtras)
|
27
|
+
|
28
|
+
module FilterExtras
|
29
|
+
def filter_param
|
30
|
+
default_filter = resource.default_filter if resource.respond_to?(:default_filter)
|
31
|
+
default_filter ||= {}
|
32
|
+
default_filter.merge(super)
|
33
|
+
end
|
34
|
+
|
35
|
+
def each_filter
|
36
|
+
super do |filter, operator, value|
|
37
|
+
unless filter.values[0][:allow_nil]
|
38
|
+
has_nil = value.nil? || value.is_a?(Array) && value.any?(&:nil?)
|
39
|
+
raise Errors::NullFilter.new(filter.keys.first) if has_nil
|
40
|
+
end
|
41
|
+
yield filter, operator, value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Only for alias, tiny diff
|
46
|
+
def filter_via_adapter(filter, operator, value)
|
47
|
+
type_name = ::Graphiti::Types.name_for(filter.values.first[:type])
|
48
|
+
method = :"filter_#{type_name}_#{operator}"
|
49
|
+
name = filter.keys.first
|
50
|
+
name = resource.all_attributes[name][:alias] || name
|
51
|
+
|
52
|
+
if resource.adapter.respond_to?(method)
|
53
|
+
resource.adapter.send(method, @scope, name, value)
|
54
|
+
else
|
55
|
+
raise ::Graphiti::Errors::AdapterNotImplemented.new \
|
56
|
+
resource.adapter, name, method
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
Graphiti::Scoping::Filter.send(:prepend, FilterExtras)
|
61
|
+
|
62
|
+
module SortAliasExtras
|
63
|
+
def each_sort
|
64
|
+
sort_param.each do |sort_hash|
|
65
|
+
name = sort_hash.keys.first
|
66
|
+
name = resource.all_attributes[name][:alias] || name
|
67
|
+
direction = sort_hash.values.first
|
68
|
+
yield name, direction
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
Graphiti::Scoping::Sort.send(:prepend, SortAliasExtras)
|
73
|
+
|
74
|
+
module PaginateExtras
|
75
|
+
def apply
|
76
|
+
if query_hash[:reverse] && (before_cursor || after_cursor)
|
77
|
+
raise ::GraphitiGql::Errors::UnsupportedLast
|
78
|
+
end
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
def offset
|
83
|
+
offset = 0
|
84
|
+
|
85
|
+
if (value = page_param[:offset])
|
86
|
+
offset = value.to_i
|
87
|
+
end
|
12
88
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
89
|
+
if before_cursor&.key?(:offset)
|
90
|
+
if page_param.key?(:number)
|
91
|
+
raise Errors::UnsupportedBeforeCursor
|
92
|
+
end
|
93
|
+
|
94
|
+
offset = before_cursor[:offset] - (size * number) - 1
|
95
|
+
offset = 0 if offset.negative?
|
96
|
+
end
|
97
|
+
|
98
|
+
if after_cursor&.key?(:offset)
|
99
|
+
offset = after_cursor[:offset]
|
100
|
+
end
|
101
|
+
|
102
|
+
offset
|
103
|
+
end
|
104
|
+
|
105
|
+
# TODO memoize
|
106
|
+
def size
|
107
|
+
size = super
|
108
|
+
if before_cursor && after_cursor
|
109
|
+
diff = before_cursor[:offset] - after_cursor[:offset] - 1
|
110
|
+
size = [size, diff].min
|
111
|
+
elsif before_cursor
|
112
|
+
comparator = query_hash[:reverse] ? :>= : :<=
|
113
|
+
if before_cursor[:offset].send(comparator, size)
|
114
|
+
diff = before_cursor[:offset] - size
|
115
|
+
size = [size, diff].min
|
116
|
+
size = 1 if size.zero?
|
117
|
+
end
|
20
118
|
end
|
119
|
+
size
|
21
120
|
end
|
22
121
|
end
|
23
|
-
|
122
|
+
Graphiti::Scoping::Paginate.send(:prepend, PaginateExtras)
|
24
123
|
|
25
|
-
|
124
|
+
module StatsExtras
|
125
|
+
def calculate_stat(name, function)
|
126
|
+
config = @resource.all_attributes[name] || {}
|
127
|
+
name = config[:alias] || name
|
128
|
+
super(name, function)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
Graphiti::Stats::Payload.send(:prepend, StatsExtras)
|
132
|
+
|
133
|
+
Graphiti::Types[:big_integer] = Graphiti::Types[:integer].dup
|
134
|
+
Graphiti::Types[:big_integer][:graphql_type] = ::GraphQL::Types::BigInt
|
135
|
+
|
136
|
+
######## support precise_datetime ###########
|
137
|
+
#############################################
|
138
|
+
definition = Dry::Types::Nominal.new(String)
|
139
|
+
_out = definition.constructor do |input|
|
140
|
+
input.utc.round(10).iso8601(6)
|
141
|
+
end
|
26
142
|
|
27
|
-
|
28
|
-
|
29
|
-
default_filter = resource.default_filter if resource.respond_to?(:default_filter)
|
30
|
-
default_filter ||= {}
|
31
|
-
default_filter.merge(super)
|
143
|
+
_in = definition.constructor do |input|
|
144
|
+
Time.zone.parse(input)
|
32
145
|
end
|
33
|
-
|
34
|
-
Graphiti
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
146
|
+
|
147
|
+
# Register it with Graphiti
|
148
|
+
Graphiti::Types[:precise_datetime] = {
|
149
|
+
params: _in,
|
150
|
+
read: _out,
|
151
|
+
write: _in,
|
152
|
+
kind: 'scalar',
|
153
|
+
canonical_name: :precise_datetime,
|
154
|
+
description: 'Datetime with milliseconds'
|
155
|
+
}
|
156
|
+
|
157
|
+
module ActiveRecordAdapterExtras
|
158
|
+
extend ActiveSupport::Concern
|
159
|
+
|
160
|
+
included do
|
161
|
+
alias_method :filter_precise_datetime_lt, :filter_lt
|
162
|
+
alias_method :filter_precise_datetime_lte, :filter_lte
|
163
|
+
alias_method :filter_precise_datetime_gt, :filter_gt
|
164
|
+
alias_method :filter_precise_datetime_gte, :filter_gte
|
165
|
+
alias_method :filter_precise_datetime_eq, :filter_eq
|
166
|
+
alias_method :filter_precise_datetime_not_eq, :filter_not_eq
|
167
|
+
end
|
168
|
+
end
|
169
|
+
Graphiti::Adapters::ActiveRecord.send(:include, ActiveRecordAdapterExtras)
|
170
|
+
|
171
|
+
Graphiti::Adapters::Abstract.class_eval do
|
172
|
+
class << self
|
173
|
+
alias :old_default_operators :default_operators
|
174
|
+
def default_operators
|
175
|
+
old_default_operators.merge(precise_datetime: numerical_operators)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
########## end support precise_datetime ############
|
180
|
+
|
181
|
+
# ==================================================
|
182
|
+
# Below is all to support pagination argument 'last'
|
183
|
+
# ==================================================
|
184
|
+
module SortExtras
|
185
|
+
def sort_param
|
186
|
+
param = super
|
187
|
+
if query_hash[:reverse]
|
188
|
+
param = [{ id: :asc }] if param == []
|
189
|
+
param = param.map do |p|
|
190
|
+
{}.tap do |hash|
|
191
|
+
dir = p[p.keys.first]
|
192
|
+
dir = dir == :asc ? :desc : :asc
|
193
|
+
hash[p.keys.first] = dir
|
194
|
+
end
|
49
195
|
end
|
50
196
|
end
|
197
|
+
param
|
51
198
|
end
|
52
|
-
param
|
53
199
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
200
|
+
Graphiti::Scoping::Sort.send(:prepend, SortExtras)
|
201
|
+
module QueryExtras
|
202
|
+
def hash
|
203
|
+
hash = super
|
204
|
+
hash[:reverse] = true if @params[:reverse]
|
205
|
+
hash
|
206
|
+
end
|
61
207
|
end
|
62
|
-
|
63
|
-
Graphiti::Query.send(:prepend, QueryExtras)
|
64
|
-
module ScopeExtras
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
208
|
+
|
209
|
+
Graphiti::Query.send(:prepend, QueryExtras)
|
210
|
+
module ScopeExtras
|
211
|
+
def resolve(*args)
|
212
|
+
results = super
|
213
|
+
results.reverse! if @query.hash[:reverse]
|
214
|
+
results
|
215
|
+
end
|
69
216
|
end
|
70
|
-
|
71
|
-
|
217
|
+
Graphiti::Scope.send(:prepend, ScopeExtras)
|
218
|
+
end
|
@@ -3,17 +3,19 @@ module GraphitiGql
|
|
3
3
|
module Fields
|
4
4
|
class Attribute
|
5
5
|
def initialize(name, config)
|
6
|
-
@name = name
|
7
6
|
@config = config
|
7
|
+
@name = name
|
8
|
+
@alias = config[:alias]
|
8
9
|
end
|
9
10
|
|
10
11
|
def apply(type)
|
11
12
|
is_nullable = !!@config[:null]
|
12
13
|
_config = @config
|
13
14
|
_name = @name
|
15
|
+
_alias = @alias
|
14
16
|
opts = @config.slice(:null, :deprecation_reason)
|
15
|
-
type.field(
|
16
|
-
type.define_method
|
17
|
+
type.field(_name, field_type, **opts)
|
18
|
+
type.define_method _name do
|
17
19
|
if (readable = _config[:readable]).is_a?(Symbol)
|
18
20
|
resource = object.instance_variable_get(:@__graphiti_resource)
|
19
21
|
unless resource.send(readable)
|
@@ -24,7 +26,7 @@ module GraphitiGql
|
|
24
26
|
value = if _config[:proc]
|
25
27
|
instance_eval(&_config[:proc])
|
26
28
|
else
|
27
|
-
object.send(_name)
|
29
|
+
object.send(_alias || _name)
|
28
30
|
end
|
29
31
|
return if value.nil?
|
30
32
|
Graphiti::Types[_config[:type]][:read].call(value)
|
@@ -58,18 +58,21 @@ module GraphitiGql
|
|
58
58
|
filter_graphql_name = "#{type_name}Filter#{filter_name.to_s.camelize(:lower)}"
|
59
59
|
klass.graphql_name(filter_graphql_name)
|
60
60
|
filter_config[:operators].keys.each do |operator|
|
61
|
-
|
62
|
-
|
63
|
-
type
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
graphiti_type = Graphiti::Types[filter_config[:type]]
|
62
|
+
type = graphiti_type[:graphql_type]
|
63
|
+
if !type
|
64
|
+
canonical_graphiti_type = Graphiti::Types
|
65
|
+
.name_for(filter_config[:type])
|
66
|
+
type = GQL_TYPE_MAP[canonical_graphiti_type]
|
67
|
+
type = String if filter_name == :id
|
68
|
+
end
|
69
|
+
|
67
70
|
if (allowlist = filter_config[:allow])
|
68
71
|
type = define_allowlist_type(filter_graphql_name, allowlist)
|
69
72
|
end
|
70
73
|
|
71
74
|
type = [type] unless !!filter_config[:single]
|
72
|
-
klass.argument operator, type, required:
|
75
|
+
klass.argument operator, type, required: false
|
73
76
|
end
|
74
77
|
klass
|
75
78
|
end
|
data/lib/graphiti_gql/schema.rb
CHANGED
@@ -1,21 +1,28 @@
|
|
1
1
|
module GraphitiGql
|
2
2
|
class Schema
|
3
|
+
class PreciseDatetime < GraphQL::Types::ISO8601DateTime
|
4
|
+
self.time_precision = 6
|
5
|
+
end
|
6
|
+
|
3
7
|
GQL_TYPE_MAP = {
|
4
8
|
integer_id: String,
|
5
9
|
string: String,
|
6
10
|
uuid: String,
|
7
11
|
integer: Integer,
|
12
|
+
big_integer: GraphQL::Types::BigInt,
|
8
13
|
float: Float,
|
9
14
|
boolean: GraphQL::Schema::Member::GraphQLTypeNames::Boolean,
|
10
15
|
date: GraphQL::Types::ISO8601Date,
|
11
16
|
datetime: GraphQL::Types::ISO8601DateTime,
|
17
|
+
precise_datetime: PreciseDatetime,
|
12
18
|
hash: GraphQL::Types::JSON,
|
13
19
|
array: [GraphQL::Types::JSON],
|
14
20
|
array_of_strings: [String],
|
15
21
|
array_of_integers: [Integer],
|
16
22
|
array_of_floats: [Float],
|
17
23
|
array_of_dates: [GraphQL::Types::ISO8601Date],
|
18
|
-
array_of_datetimes: [GraphQL::Types::ISO8601DateTime]
|
24
|
+
array_of_datetimes: [GraphQL::Types::ISO8601DateTime],
|
25
|
+
array_of_precise_datetimes: [PreciseDatetime]
|
19
26
|
}
|
20
27
|
|
21
28
|
class RelayConnectionExtension < GraphQL::Schema::Field::ConnectionExtension
|
@@ -25,6 +32,14 @@ module GraphitiGql
|
|
25
32
|
end
|
26
33
|
end
|
27
34
|
|
35
|
+
def self.registry
|
36
|
+
Registry.instance
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.print
|
40
|
+
GraphQL::Schema::Printer.print_schema(GraphitiGql.schema)
|
41
|
+
end
|
42
|
+
|
28
43
|
def initialize(resources)
|
29
44
|
@resources = resources
|
30
45
|
end
|
@@ -35,6 +50,7 @@ module GraphitiGql
|
|
35
50
|
klass.use(GraphQL::Batch)
|
36
51
|
klass.connections.add(ResponseShim, Connection)
|
37
52
|
klass.connections.add(Array, ToManyConnection)
|
53
|
+
klass.orphan_types [GraphQL::Types::JSON]
|
38
54
|
klass
|
39
55
|
end
|
40
56
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module GraphitiGql
|
2
|
+
module SpecHelper
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class Node < OpenStruct
|
6
|
+
def decoded_id
|
7
|
+
Base64.decode64(self.id)
|
8
|
+
end
|
9
|
+
|
10
|
+
def int_id
|
11
|
+
decoded_id.to_i
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Util
|
16
|
+
def self.underscore(hash)
|
17
|
+
hash.deep_transform_keys { |k| k.to_s.underscore.to_sym }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def query
|
22
|
+
name = Schema.registry.key_for(resource)
|
23
|
+
q = %|
|
24
|
+
query #{name} (
|
25
|
+
$filter: #{name}Filter,
|
26
|
+
$sort: [#{name}Sort!],
|
27
|
+
$first: Int,
|
28
|
+
$last: Int,
|
29
|
+
$before: String,
|
30
|
+
$after: String,
|
31
|
+
) {
|
32
|
+
#{resource.graphql_entrypoint} (
|
33
|
+
filter: $filter,
|
34
|
+
sort: $sort,
|
35
|
+
first: $first,
|
36
|
+
last: $last,
|
37
|
+
before: $before,
|
38
|
+
after: $after,
|
39
|
+
) {
|
40
|
+
edges {
|
41
|
+
node {|
|
42
|
+
|
43
|
+
fields.each do |name|
|
44
|
+
q << %|
|
45
|
+
#{name.to_s.camelize(:lower)}|
|
46
|
+
end
|
47
|
+
|
48
|
+
q << %|
|
49
|
+
}
|
50
|
+
}
|
51
|
+
pageInfo {
|
52
|
+
startCursor
|
53
|
+
endCursor
|
54
|
+
hasNextPage
|
55
|
+
hasPreviousPage
|
56
|
+
}|
|
57
|
+
|
58
|
+
if params[:stats]
|
59
|
+
q << %|
|
60
|
+
stats {|
|
61
|
+
params[:stats].each_pair do |name, calculations|
|
62
|
+
q << %|
|
63
|
+
#{name.to_s.camelize(:lower)} {|
|
64
|
+
Array(calculations).each do |calc|
|
65
|
+
q << %|
|
66
|
+
#{calc.to_s.camelize(:lower)}|
|
67
|
+
end
|
68
|
+
|
69
|
+
q << %|
|
70
|
+
}|
|
71
|
+
end
|
72
|
+
q << %|
|
73
|
+
}|
|
74
|
+
end
|
75
|
+
|
76
|
+
q << %|
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
|
80
|
+
|
81
|
+
q
|
82
|
+
end
|
83
|
+
|
84
|
+
def run
|
85
|
+
lambda do
|
86
|
+
gql_params = params.deep_transform_keys { |key| key.to_s.camelize(:lower).to_sym }
|
87
|
+
(gql_params[:sort] || []).each do |sort|
|
88
|
+
sort[:att] = sort[:att].to_s.camelize(:lower)
|
89
|
+
sort[:dir] = sort[:dir].to_s
|
90
|
+
end
|
91
|
+
GraphitiGql.run(query, gql_params, ctx).deep_symbolize_keys
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def run!
|
96
|
+
@response = run.call
|
97
|
+
end
|
98
|
+
|
99
|
+
def response
|
100
|
+
@response ||= run!
|
101
|
+
end
|
102
|
+
|
103
|
+
def errors
|
104
|
+
response[:errors]
|
105
|
+
end
|
106
|
+
|
107
|
+
def error_messages
|
108
|
+
response[:errors].map { |e| e[:message] }
|
109
|
+
end
|
110
|
+
|
111
|
+
def nodes
|
112
|
+
return [] unless data
|
113
|
+
nodes = edges.map { |e| Util.underscore(e[:node]) }
|
114
|
+
nodes.map { |n| ::GraphitiGql::SpecHelper::Node.new(n) }
|
115
|
+
end
|
116
|
+
|
117
|
+
def data
|
118
|
+
if response.key?(:data)
|
119
|
+
response[:data]
|
120
|
+
else
|
121
|
+
raise "Tried to access 'data', but these errors were returned instead: #{error_messages.join(". ")}."
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def edges
|
126
|
+
data[data.keys.first][:edges]
|
127
|
+
end
|
128
|
+
|
129
|
+
def stats
|
130
|
+
Util.underscore(data[data.keys.first][:stats])
|
131
|
+
end
|
132
|
+
|
133
|
+
def page_info
|
134
|
+
Util.underscore(data[data.keys.first][:pageInfo])
|
135
|
+
end
|
136
|
+
|
137
|
+
included do
|
138
|
+
let(:params) { {} }
|
139
|
+
let(:resource) { described_class }
|
140
|
+
let(:ctx) { {} }
|
141
|
+
let(:fields) do
|
142
|
+
fields = []
|
143
|
+
resource.attributes.each_pair do |name, config|
|
144
|
+
(fields << name) if config[:readable]
|
145
|
+
end
|
146
|
+
fields
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
data/lib/graphiti_gql/version.rb
CHANGED
data/lib/graphiti_gql.rb
CHANGED
@@ -29,6 +29,10 @@ require "graphiti_gql/engine" if defined?(Rails)
|
|
29
29
|
module GraphitiGql
|
30
30
|
class Error < StandardError; end
|
31
31
|
|
32
|
+
class Configuration
|
33
|
+
attr_accessor :application_controller
|
34
|
+
end
|
35
|
+
|
32
36
|
def self.schema!
|
33
37
|
Schema::Registry.instance.clear
|
34
38
|
resources ||= Graphiti.resources.reject(&:abstract_class?)
|
@@ -39,6 +43,14 @@ module GraphitiGql
|
|
39
43
|
@schema
|
40
44
|
end
|
41
45
|
|
46
|
+
def self.config
|
47
|
+
@config ||= Configuration.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.configure
|
51
|
+
yield config
|
52
|
+
end
|
53
|
+
|
42
54
|
def self.entrypoints=(val)
|
43
55
|
@entrypoints = val
|
44
56
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphiti_gql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lee Richmond
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-06-
|
11
|
+
date: 2022-06-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -108,7 +108,7 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '7.0'
|
111
|
-
description:
|
111
|
+
description:
|
112
112
|
email:
|
113
113
|
- richmolj@gmail.com
|
114
114
|
executables: []
|
@@ -160,13 +160,14 @@ files:
|
|
160
160
|
- lib/graphiti_gql/schema/registry.rb
|
161
161
|
- lib/graphiti_gql/schema/resource_type.rb
|
162
162
|
- lib/graphiti_gql/schema/util.rb
|
163
|
+
- lib/graphiti_gql/spec_helper.rb
|
163
164
|
- lib/graphiti_gql/version.rb
|
164
165
|
homepage: https://www.graphiti.dev
|
165
166
|
licenses:
|
166
167
|
- MIT
|
167
168
|
metadata:
|
168
169
|
homepage_uri: https://www.graphiti.dev
|
169
|
-
post_install_message:
|
170
|
+
post_install_message:
|
170
171
|
rdoc_options: []
|
171
172
|
require_paths:
|
172
173
|
- lib
|
@@ -181,8 +182,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
181
182
|
- !ruby/object:Gem::Version
|
182
183
|
version: '0'
|
183
184
|
requirements: []
|
184
|
-
rubygems_version: 3.3.
|
185
|
-
signing_key:
|
185
|
+
rubygems_version: 3.0.3.1
|
186
|
+
signing_key:
|
186
187
|
specification_version: 4
|
187
188
|
summary: GraphQL support for Graphiti
|
188
189
|
test_files: []
|