hq-graphql 2.0.7 → 2.1.1
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 +28 -0
- data/lib/hq-graphql.rb +0 -2
- data/lib/hq/graphql.rb +28 -21
- data/lib/hq/graphql/active_record_extensions.rb +23 -27
- data/lib/hq/graphql/association_loader.rb +49 -0
- data/lib/hq/graphql/comparator.rb +1 -1
- data/lib/hq/graphql/config.rb +17 -11
- data/lib/hq/graphql/engine.rb +0 -1
- data/lib/hq/graphql/enum.rb +77 -0
- data/lib/hq/graphql/enum/sort_by.rb +10 -0
- data/lib/hq/graphql/enum/sort_order.rb +10 -0
- data/lib/hq/graphql/field.rb +12 -11
- data/lib/hq/graphql/field_extension/association_loader_extension.rb +15 -0
- data/lib/hq/graphql/field_extension/paginated_arguments.rb +22 -0
- data/lib/hq/graphql/field_extension/paginated_loader.rb +45 -0
- data/lib/hq/graphql/input_object.rb +12 -7
- data/lib/hq/graphql/inputs.rb +4 -3
- data/lib/hq/graphql/mutation.rb +0 -1
- data/lib/hq/graphql/object.rb +42 -11
- data/lib/hq/graphql/object_association.rb +50 -0
- data/lib/hq/graphql/paginated_association_loader.rb +158 -0
- data/lib/hq/graphql/resource.rb +47 -156
- data/lib/hq/graphql/resource/auto_mutation.rb +163 -0
- data/lib/hq/graphql/root_mutation.rb +1 -2
- data/lib/hq/graphql/root_query.rb +0 -1
- data/lib/hq/graphql/scalars.rb +0 -1
- data/lib/hq/graphql/schema.rb +1 -1
- data/lib/hq/graphql/types.rb +22 -8
- data/lib/hq/graphql/types/object.rb +7 -11
- data/lib/hq/graphql/types/uuid.rb +7 -14
- data/lib/hq/graphql/version.rb +1 -2
- metadata +12 -39
- data/lib/hq/graphql/loaders.rb +0 -4
- data/lib/hq/graphql/loaders/association.rb +0 -52
- data/lib/hq/graphql/resource/mutation.rb +0 -39
data/lib/hq/graphql/resource.rb
CHANGED
@@ -1,21 +1,26 @@
|
|
1
|
-
# typed: false
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
require "hq/graphql/
|
3
|
+
require "hq/graphql/enum/sort_by"
|
4
|
+
require "hq/graphql/field_extension/paginated_arguments"
|
5
|
+
require "hq/graphql/input_object"
|
6
|
+
require "hq/graphql/object"
|
7
|
+
require "hq/graphql/resource/auto_mutation"
|
8
|
+
require "hq/graphql/scalars"
|
5
9
|
|
6
10
|
module HQ
|
7
11
|
module GraphQL
|
8
12
|
module Resource
|
9
|
-
extend T::Helpers
|
10
|
-
|
11
13
|
def self.included(base)
|
12
14
|
super
|
13
|
-
::HQ::GraphQL.
|
15
|
+
::HQ::GraphQL.resources << base
|
14
16
|
base.include Scalars
|
15
17
|
base.include ::GraphQL::Types
|
18
|
+
base.extend ClassMethods
|
16
19
|
end
|
17
20
|
|
18
21
|
module ClassMethods
|
22
|
+
include AutoMutation
|
23
|
+
|
19
24
|
attr_writer :graphql_name, :model_name
|
20
25
|
|
21
26
|
def scope(context)
|
@@ -39,7 +44,7 @@ module HQ
|
|
39
44
|
end
|
40
45
|
|
41
46
|
def model_name
|
42
|
-
@model_name ||
|
47
|
+
@model_name || ::HQ::GraphQL.extract_class(self)
|
43
48
|
end
|
44
49
|
|
45
50
|
def model_klass
|
@@ -62,6 +67,10 @@ module HQ
|
|
62
67
|
@query_klass ||= build_graphql_object
|
63
68
|
end
|
64
69
|
|
70
|
+
def sort_fields_enum
|
71
|
+
@sort_fields_enum || ::HQ::GraphQL::Enum::SortBy
|
72
|
+
end
|
73
|
+
|
65
74
|
protected
|
66
75
|
|
67
76
|
def default_scope(&block)
|
@@ -73,155 +82,25 @@ module HQ
|
|
73
82
|
end
|
74
83
|
|
75
84
|
def mutations(create: true, copy: true, update: true, destroy: true)
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
if create
|
81
|
-
create_mutation = ::HQ::GraphQL::Resource::Mutation.build(model_name, action: :create, graphql_name: "#{scoped_graphql_name}Create") do
|
82
|
-
define_method(:resolve) do |**args|
|
83
|
-
resource = scoped_self.new_record(context)
|
84
|
-
resource.assign_attributes(args[:attributes].format_nested_attributes)
|
85
|
-
if resource.save
|
86
|
-
{
|
87
|
-
resource: resource,
|
88
|
-
errors: {},
|
89
|
-
}
|
90
|
-
else
|
91
|
-
{
|
92
|
-
resource: nil,
|
93
|
-
errors: errors_from_resource(resource)
|
94
|
-
}
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
lazy_load do
|
99
|
-
argument :attributes, ::HQ::GraphQL::Inputs[scoped_model_name], required: true
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
mutation_klasses["create_#{scoped_graphql_name.underscore}"] = create_mutation
|
104
|
-
end
|
105
|
-
|
106
|
-
if copy
|
107
|
-
copy_mutation = ::HQ::GraphQL::Resource::Mutation.build(
|
108
|
-
model_name,
|
109
|
-
action: :copy,
|
110
|
-
graphql_name: "#{scoped_graphql_name}Copy",
|
111
|
-
require_primary_key: true,
|
112
|
-
nil_klass: true
|
113
|
-
) do
|
114
|
-
define_method(:resolve) do |**args|
|
115
|
-
resource = scoped_self.find_record(args, context)
|
116
|
-
|
117
|
-
if resource
|
118
|
-
copy = resource.copy
|
119
|
-
if copy.save
|
120
|
-
{
|
121
|
-
resource: copy,
|
122
|
-
errors: {},
|
123
|
-
}
|
124
|
-
else
|
125
|
-
{
|
126
|
-
resource: copy,
|
127
|
-
errors: errors_from_resource(copy)
|
128
|
-
}
|
129
|
-
end
|
130
|
-
else
|
131
|
-
{
|
132
|
-
resource: nil,
|
133
|
-
errors: { resource: "Unable to find #{scoped_graphql_name}" }
|
134
|
-
}
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
mutation_klasses["copy_#{scoped_graphql_name.underscore}"] = copy_mutation
|
140
|
-
end
|
141
|
-
|
142
|
-
if update
|
143
|
-
update_mutation = ::HQ::GraphQL::Resource::Mutation.build(
|
144
|
-
model_name,
|
145
|
-
action: :update,
|
146
|
-
graphql_name: "#{scoped_graphql_name}Update",
|
147
|
-
require_primary_key: true
|
148
|
-
) do
|
149
|
-
define_method(:resolve) do |**args|
|
150
|
-
resource = scoped_self.find_record(args, context)
|
151
|
-
|
152
|
-
if resource
|
153
|
-
resource.assign_attributes(args[:attributes].format_nested_attributes)
|
154
|
-
if resource.save
|
155
|
-
{
|
156
|
-
resource: resource,
|
157
|
-
errors: {},
|
158
|
-
}
|
159
|
-
else
|
160
|
-
{
|
161
|
-
resource: nil,
|
162
|
-
errors: errors_from_resource(resource)
|
163
|
-
}
|
164
|
-
end
|
165
|
-
else
|
166
|
-
{
|
167
|
-
resource: nil,
|
168
|
-
errors: { resource: "Unable to find #{scoped_graphql_name}" }
|
169
|
-
}
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
lazy_load do
|
174
|
-
argument :attributes, ::HQ::GraphQL::Inputs[scoped_model_name], required: true
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
mutation_klasses["update_#{scoped_graphql_name.underscore}"] = update_mutation
|
179
|
-
end
|
180
|
-
|
181
|
-
if destroy
|
182
|
-
destroy_mutation = ::HQ::GraphQL::Resource::Mutation.build(
|
183
|
-
model_name,
|
184
|
-
action: :destroy,
|
185
|
-
graphql_name: "#{scoped_graphql_name}Destroy",
|
186
|
-
require_primary_key: true
|
187
|
-
) do
|
188
|
-
define_method(:resolve) do |**attrs|
|
189
|
-
resource = scoped_self.find_record(attrs, context)
|
190
|
-
|
191
|
-
if resource
|
192
|
-
if resource.destroy
|
193
|
-
{
|
194
|
-
resource: resource,
|
195
|
-
errors: {},
|
196
|
-
}
|
197
|
-
else
|
198
|
-
{
|
199
|
-
resource: nil,
|
200
|
-
errors: errors_from_resource(resource)
|
201
|
-
}
|
202
|
-
end
|
203
|
-
else
|
204
|
-
{
|
205
|
-
resource: nil,
|
206
|
-
errors: { resource: "Unable to find #{scoped_graphql_name}" }
|
207
|
-
}
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
mutation_klasses["destroy_#{scoped_graphql_name.underscore}"] = destroy_mutation
|
213
|
-
end
|
85
|
+
mutation_klasses["create_#{graphql_name.underscore}"] = build_create if create
|
86
|
+
mutation_klasses["copy_#{graphql_name.underscore}"] = build_copy if copy
|
87
|
+
mutation_klasses["update_#{graphql_name.underscore}"] = build_update if update
|
88
|
+
mutation_klasses["destroy_#{graphql_name.underscore}"] = build_destroy if destroy
|
214
89
|
end
|
215
90
|
|
216
91
|
def query(**options, &block)
|
217
92
|
@query_klass = build_graphql_object(**options, &block)
|
218
93
|
end
|
219
94
|
|
95
|
+
def sort_fields(*fields)
|
96
|
+
self.sort_fields_enum = fields
|
97
|
+
end
|
98
|
+
|
220
99
|
def def_root(field_name, is_array: false, null: true, &block)
|
221
|
-
|
100
|
+
resource = self
|
222
101
|
resolver = -> {
|
223
102
|
Class.new(::GraphQL::Schema::Resolver) do
|
224
|
-
type = is_array ? [
|
103
|
+
type = is_array ? [resource.query_klass] : resource.query_klass
|
225
104
|
type type, null: null
|
226
105
|
class_eval(&block) if block
|
227
106
|
end
|
@@ -231,7 +110,7 @@ module HQ
|
|
231
110
|
}
|
232
111
|
end
|
233
112
|
|
234
|
-
def root_query(find_one: true, find_all: true, pagination:
|
113
|
+
def root_query(find_one: true, find_all: true, pagination: true, limit_max: 250)
|
235
114
|
field_name = graphql_name.underscore
|
236
115
|
scoped_self = self
|
237
116
|
|
@@ -250,18 +129,22 @@ module HQ
|
|
250
129
|
|
251
130
|
if find_all
|
252
131
|
def_root field_name.pluralize, is_array: true, null: false do
|
253
|
-
|
254
|
-
argument :per_page, Integer, required: false
|
132
|
+
extension FieldExtension::PaginatedArguments, klass: scoped_self.model_klass if pagination
|
255
133
|
|
256
|
-
define_method(:resolve) do |
|
134
|
+
define_method(:resolve) do |limit: nil, offset: nil, sort_by: nil, sort_order: nil, **_attrs|
|
257
135
|
scope = scoped_self.scope(context).all
|
258
136
|
|
259
|
-
if pagination || page ||
|
260
|
-
|
261
|
-
limit = [
|
262
|
-
scope = scope.limit(limit).offset(
|
137
|
+
if pagination || page || limit
|
138
|
+
offset = [0, *offset].max
|
139
|
+
limit = [[limit_max, *limit].min, 0].max
|
140
|
+
scope = scope.limit(limit).offset(offset)
|
263
141
|
end
|
264
142
|
|
143
|
+
sort_by ||= :updated_at
|
144
|
+
sort_order ||= :desc
|
145
|
+
# There should be no risk for SQL injection since an enum is being used for both sort_by and sort_order
|
146
|
+
scope = scope.reorder(sort_by => sort_order)
|
147
|
+
|
265
148
|
scope
|
266
149
|
end
|
267
150
|
end
|
@@ -293,9 +176,17 @@ module HQ
|
|
293
176
|
class_eval(&block) if block
|
294
177
|
end
|
295
178
|
end
|
296
|
-
end
|
297
179
|
|
298
|
-
|
180
|
+
def sort_fields_enum=(fields)
|
181
|
+
@sort_fields_enum ||= Class.new(::HQ::GraphQL::Enum::SortBy).tap do |c|
|
182
|
+
c.graphql_name "#{graphql_name}Sort"
|
183
|
+
end
|
184
|
+
|
185
|
+
Array(fields).each do |field|
|
186
|
+
@sort_fields_enum.value field.to_s.classify, value: field
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
299
190
|
end
|
300
191
|
end
|
301
192
|
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hq/graphql/inputs"
|
4
|
+
require "hq/graphql/mutation"
|
5
|
+
require "hq/graphql/types"
|
6
|
+
|
7
|
+
module HQ
|
8
|
+
module GraphQL
|
9
|
+
module Resource
|
10
|
+
module AutoMutation
|
11
|
+
def build_create
|
12
|
+
scoped_self = self
|
13
|
+
|
14
|
+
build_mutation(action: :create) do
|
15
|
+
define_method(:resolve) do |**args|
|
16
|
+
resource = scoped_self.new_record(context)
|
17
|
+
resource.assign_attributes(args[:attributes].format_nested_attributes)
|
18
|
+
if resource.save
|
19
|
+
{
|
20
|
+
resource: resource,
|
21
|
+
errors: {},
|
22
|
+
}
|
23
|
+
else
|
24
|
+
{
|
25
|
+
resource: nil,
|
26
|
+
errors: errors_from_resource(resource)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
lazy_load do
|
32
|
+
argument :attributes, ::HQ::GraphQL::Inputs[scoped_self.model_name], required: true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_update
|
38
|
+
scoped_self = self
|
39
|
+
|
40
|
+
build_mutation(action: :update, require_primary_key: true) do
|
41
|
+
define_method(:resolve) do |**args|
|
42
|
+
resource = scoped_self.find_record(args, context)
|
43
|
+
|
44
|
+
if resource
|
45
|
+
resource.assign_attributes(args[:attributes].format_nested_attributes)
|
46
|
+
if resource.save
|
47
|
+
{
|
48
|
+
resource: resource,
|
49
|
+
errors: {},
|
50
|
+
}
|
51
|
+
else
|
52
|
+
{
|
53
|
+
resource: nil,
|
54
|
+
errors: errors_from_resource(resource)
|
55
|
+
}
|
56
|
+
end
|
57
|
+
else
|
58
|
+
{
|
59
|
+
resource: nil,
|
60
|
+
errors: { resource: "Unable to find #{self.class.graphql_name}" }
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
lazy_load do
|
66
|
+
argument :attributes, ::HQ::GraphQL::Inputs[scoped_self.model_name], required: true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def build_copy
|
72
|
+
scoped_self = self
|
73
|
+
|
74
|
+
build_mutation(action: :copy, require_primary_key: true, nil_klass: true) do
|
75
|
+
define_method(:resolve) do |**args|
|
76
|
+
resource = scoped_self.find_record(args, context)
|
77
|
+
|
78
|
+
if resource
|
79
|
+
copy = resource.copy
|
80
|
+
if copy.save
|
81
|
+
{
|
82
|
+
resource: copy,
|
83
|
+
errors: {},
|
84
|
+
}
|
85
|
+
else
|
86
|
+
{
|
87
|
+
resource: copy,
|
88
|
+
errors: errors_from_resource(copy)
|
89
|
+
}
|
90
|
+
end
|
91
|
+
else
|
92
|
+
{
|
93
|
+
resource: nil,
|
94
|
+
errors: { resource: "Unable to find #{self.class.graphql_name}" }
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def build_destroy
|
102
|
+
scoped_self = self
|
103
|
+
|
104
|
+
build_mutation(action: :destroy, require_primary_key: true) do
|
105
|
+
define_method(:resolve) do |**attrs|
|
106
|
+
resource = scoped_self.find_record(attrs, context)
|
107
|
+
|
108
|
+
if resource
|
109
|
+
if resource.destroy
|
110
|
+
{
|
111
|
+
resource: resource,
|
112
|
+
errors: {},
|
113
|
+
}
|
114
|
+
else
|
115
|
+
{
|
116
|
+
resource: nil,
|
117
|
+
errors: errors_from_resource(resource)
|
118
|
+
}
|
119
|
+
end
|
120
|
+
else
|
121
|
+
{
|
122
|
+
resource: nil,
|
123
|
+
errors: { resource: "Unable to find #{self.class.graphql_name}" }
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def build_mutation(action:, require_primary_key: false, nil_klass: false, &block)
|
131
|
+
gql_name = "#{graphql_name}#{action.to_s.titleize}"
|
132
|
+
scoped_model_name = model_name
|
133
|
+
Class.new(::HQ::GraphQL::Mutation) do
|
134
|
+
graphql_name gql_name
|
135
|
+
|
136
|
+
define_method(:ready?) do |*args|
|
137
|
+
super(*args) && ::HQ::GraphQL.authorized?(action, scoped_model_name, context)
|
138
|
+
end
|
139
|
+
|
140
|
+
lazy_load do
|
141
|
+
field :errors, ::HQ::GraphQL::Types::Object, null: false
|
142
|
+
field :resource, ::HQ::GraphQL::Types[scoped_model_name, nil_klass], null: true
|
143
|
+
end
|
144
|
+
|
145
|
+
instance_eval(&block)
|
146
|
+
|
147
|
+
if require_primary_key
|
148
|
+
lazy_load do
|
149
|
+
klass = scoped_model_name.constantize
|
150
|
+
primary_key = klass.primary_key
|
151
|
+
argument primary_key, ::GraphQL::Types::ID, required: true
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def errors_from_resource(resource)
|
156
|
+
resource.errors.to_h.deep_transform_keys { |k| k.to_s.camelize(:lower) }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# typed: true
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module HQ
|
@@ -8,7 +7,7 @@ module HQ
|
|
8
7
|
super
|
9
8
|
base.class_eval do
|
10
9
|
lazy_load do
|
11
|
-
::HQ::GraphQL.
|
10
|
+
::HQ::GraphQL.resources.each do |type|
|
12
11
|
type.mutation_klasses.each do |mutation_name, klass|
|
13
12
|
field mutation_name, mutation: klass
|
14
13
|
end
|