hq-graphql 2.0.11 → 2.1.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: 40c69615fc4b2b0d14d3c510d5c4973aa5a0f7bf0285278ca98505af62d73fb0
4
- data.tar.gz: 4f0926421e6e2f8658f9e6aaf0234756eaf66c6dbf126210f56a92f0d4701a8a
3
+ metadata.gz: 05c40fc2e6eaccc1277c1b6f36b19983af507721b011d6d25c9010f877c18db7
4
+ data.tar.gz: cf67ac6f3d02f3c6a5c5b0f1124e8aacd706d1e30b0077af69cad336d5dc1475
5
5
  SHA512:
6
- metadata.gz: 945bad36e906b28a2c1444f7e87df594c306b817c61f3bdfa1176f061c645904e9206f663fe82861b0a44ee61c881d8899d6979dc57842b1920070d6a48fa6ff
7
- data.tar.gz: 82cf817c3d801273c0cc4a099aa513585ba96220fc9b9ce84f996bd14cbe04d248a8d759d864fc964d7f07571bb11169c59cf670d7893ce1ea860a4d41dd86d8
6
+ metadata.gz: 2f643af54cb80d2325dc8829c620f9c4d0c48a950aced92cb0313b2aacba52a3b797f42deb0c1f8848b9454cf70d014c4795f64d5e34444d87cc4fdbe36702c8
7
+ data.tar.gz: df55f4034030bed0f035a170d581bfe030f62a2545b2f4b7d15d48838c9777a7e4aa357e5f86618ee7bb692ba001c263df9973f8e546f8a1a902d0e8f61d4821
@@ -32,14 +32,20 @@ module HQ
32
32
  config.extract_class.call(klass)
33
33
  end
34
34
 
35
- def self.resource_lookup(klass)
36
- config.resource_lookup.call(klass)
35
+ def self.lookup_resource(klass)
36
+ [klass, klass.base_class, klass.superclass].lazy.map do |k|
37
+ config.resource_lookup.call(k) || resources.detect { |r| r.model_klass == k }
38
+ end.reject(&:nil?).first
39
+ end
40
+
41
+ def self.use_experimental_associations?
42
+ !!config.use_experimental_associations
37
43
  end
38
44
 
39
45
  def self.reset!
40
46
  @root_queries = nil
41
47
  @enums = nil
42
- @types = nil
48
+ @resources = nil
43
49
  ::HQ::GraphQL::Inputs.reset!
44
50
  ::HQ::GraphQL::Types.reset!
45
51
  end
@@ -52,8 +58,8 @@ module HQ
52
58
  @enums ||= Set.new
53
59
  end
54
60
 
55
- def self.types
56
- @types ||= Set.new
61
+ def self.resources
62
+ @resources ||= Set.new
57
63
  end
58
64
  end
59
65
  end
@@ -67,6 +73,7 @@ require "hq/graphql/inputs"
67
73
  require "hq/graphql/input_object"
68
74
  require "hq/graphql/mutation"
69
75
  require "hq/graphql/object"
76
+ require "hq/graphql/paginated_association_loader"
70
77
  require "hq/graphql/resource"
71
78
  require "hq/graphql/root_mutation"
72
79
  require "hq/graphql/root_query"
@@ -8,6 +8,7 @@ module HQ
8
8
  :default_scope,
9
9
  :extract_class,
10
10
  :resource_lookup,
11
+ :use_experimental_associations,
11
12
  keyword_init: true
12
13
  )
13
14
  def initialize(
@@ -73,3 +73,6 @@ module HQ::GraphQL
73
73
  end
74
74
  end
75
75
  end
76
+
77
+ require "hq/graphql/enum/sort_by"
78
+ require "hq/graphql/enum/sort_order"
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HQ
4
+ class GraphQL::Enum::SortBy < ::HQ::GraphQL::Enum
5
+ value "CreatedAt", value: :created_at
6
+ value "UpdatedAt", value: :updated_at
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HQ
4
+ class GraphQL::Enum::SortOrder < ::HQ::GraphQL::Enum
5
+ value "ASC", value: :asc
6
+ value "DESC", value: :desc
7
+ end
8
+ end
@@ -3,13 +3,13 @@
3
3
  module HQ
4
4
  module GraphQL
5
5
  class Field < ::GraphQL::Schema::Field
6
- attr_reader :authorize_action, :authorize, :klass
6
+ attr_reader :authorize_action, :authorize
7
7
 
8
8
  def initialize(*args, authorize_action: :read, authorize: nil, klass: nil, **options, &block)
9
9
  super(*args, **options, &block)
10
10
  @authorize_action = authorize_action
11
11
  @authorize = authorize
12
- @klass = klass
12
+ @class_name = klass
13
13
  end
14
14
 
15
15
  def authorized?(object, ctx)
@@ -20,13 +20,34 @@ module HQ
20
20
 
21
21
  def resolve_field(object, args, ctx)
22
22
  if klass.present? && !!::GraphQL::Batch::Executor.current && object.object
23
- AssociationLoader.for(klass.constantize, original_name).load(object.object).then do
24
- super
25
- end
23
+ loader =
24
+ if ::HQ::GraphQL.use_experimental_associations?
25
+ limit = args[:limit]
26
+ offset = args[:offset]
27
+ sort_by = args[:sortBy]
28
+ sort_order = args[:sortOrder]
29
+
30
+ PaginatedAssociationLoader.for(
31
+ klass,
32
+ original_name,
33
+ limit: limit,
34
+ offset: offset,
35
+ sort_by: sort_by,
36
+ sort_order: sort_order
37
+ )
38
+ else
39
+ AssociationLoader.for(klass, original_name)
40
+ end
41
+
42
+ loader.load(object.object)
26
43
  else
27
44
  super
28
45
  end
29
46
  end
47
+
48
+ def klass
49
+ @klass ||= @class_name&.constantize
50
+ end
30
51
  end
31
52
  end
32
53
  end
@@ -24,14 +24,10 @@ module HQ
24
24
 
25
25
  def klass_for(klass_or_string)
26
26
  klass = klass_or_string.is_a?(String) ? klass_or_string.constantize : klass_or_string
27
- type = find_type(klass)
27
+ resource = ::HQ::GraphQL.lookup_resource(klass)
28
28
 
29
- raise(Error, Error::MISSING_TYPE_MSG % { klass: klass.name }) if !type
30
- type.input_klass
31
- end
32
-
33
- def find_type(klass)
34
- ::HQ::GraphQL.resource_lookup(klass) || ::HQ::GraphQL.types.detect { |t| t.model_klass == klass }
29
+ raise(Error, Error::MISSING_TYPE_MSG % { klass: klass.name }) if !resource
30
+ resource.input_klass
35
31
  end
36
32
  end
37
33
  end
@@ -47,11 +47,22 @@ module HQ
47
47
  end
48
48
 
49
49
  def field_from_association(association, auto_nil:)
50
- type = ::HQ::GraphQL::Types[association.klass]
50
+ # The PaginationAssociationLoader doesn't support through associations yet
51
+ return if association.through_reflection? && ::HQ::GraphQL.use_experimental_associations?
52
+
53
+ association_klass = association.klass
54
+ type = ::HQ::GraphQL::Types[association_klass]
51
55
  name = association.name
52
56
  case association.macro
53
57
  when :has_many
54
- field name, [type], null: false, klass: model_name
58
+ field name, [type], null: false, klass: model_name do
59
+ if ::HQ::GraphQL.use_experimental_associations? && (resource = ::HQ::GraphQL.lookup_resource(association_klass))
60
+ argument :offset, Integer, required: false
61
+ argument :limit, Integer, required: false
62
+ argument :sort_by, resource.sort_fields_enum, required: false
63
+ argument :sort_order, Enum::SortOrder, required: false
64
+ end
65
+ end
55
66
  else
56
67
  field name, type, null: !auto_nil || !association_required?(association), klass: model_name
57
68
  end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HQ
4
+ module GraphQL
5
+ class PaginatedAssociationLoader < ::GraphQL::Batch::Loader
6
+ def initialize(model, association_name, limit: nil, offset: nil, sort_by: nil, sort_order: nil)
7
+ @model = model
8
+ @association_name = association_name
9
+ @limit = [0, limit].max if limit
10
+ @offset = [0, offset].max if offset
11
+ @sort_by = sort_by || :updated_at
12
+ @sort_order = normalize_sort_order(sort_order)
13
+
14
+ validate!
15
+ end
16
+
17
+ def load(record)
18
+ raise TypeError, "#{@model} loader can't load association for #{record.class}" unless record.is_a?(@model)
19
+ super
20
+ end
21
+
22
+ def cache_key(record)
23
+ record.send(primary_key)
24
+ end
25
+
26
+ def perform(records)
27
+ scope =
28
+ if @limit || @offset
29
+ # If a limit or offset is added, then we need to transform the query
30
+ # into a lateral join so that we can limit on groups of data.
31
+ #
32
+ # > SELECT * FROM addresses WHERE addresses.user_id IN ($1, $2, ..., $N) ORDER BY addresses.created_at DESC;
33
+ # ...becomes
34
+ # > SELECT DISTINCT a_top.*
35
+ # > FROM addresses
36
+ # > INNER JOIN LATERAL (
37
+ # > SELECT inner.*
38
+ # > FROM addresses inner
39
+ # > WHERE inner.user_id = addresses.user_id
40
+ # > ORDER BY inner.created_at DESC
41
+ # > LIMIT 1
42
+ # > ) a_top ON TRUE
43
+ # > WHERE addresses.user_id IN ($1, $2, ..., $N)
44
+ # > ORDER BY a_top.created_at DESC
45
+ inner_table = association_class.arel_table
46
+ association_table = inner_table.alias("outer")
47
+
48
+ inside_scope = default_scope.
49
+ select(inner_table[::Arel.star]).
50
+ from(inner_table).
51
+ where(inner_table[association_key].eq(association_table[association_key])).
52
+ reorder(arel_order(inner_table)).
53
+ limit(@limit).
54
+ offset(@offset)
55
+
56
+ outside_table = ::Arel::Table.new("top")
57
+ association_class.
58
+ select(outside_table[::Arel.star]).distinct.
59
+ from(association_table).
60
+ joins("INNER JOIN LATERAL (#{inside_scope.to_sql}) #{outside_table.name} ON TRUE").
61
+ where(association_table[association_key].in(records.map { |r| join_value(r) })).
62
+ reorder(arel_order(outside_table))
63
+ else
64
+ default_scope.
65
+ reorder(arel_order(association_class.arel_table)).
66
+ where(association_key => records.map { |r| join_value(r) })
67
+ end
68
+
69
+ results = scope.to_a
70
+ records.each do |record|
71
+ fulfill(record, association_value(record, results)) unless fulfilled?(record)
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def association_key
78
+ belongs_to? ? association.association_primary_key : association.foreign_key
79
+ end
80
+
81
+ def association_value(record, results)
82
+ enumerator = has_many? ? :select : :detect
83
+ results.send(enumerator) { |r| r.send(association_key) == join_value(record) }
84
+ end
85
+
86
+ def join_key
87
+ belongs_to? ? association.foreign_key : association.association_primary_key
88
+ end
89
+
90
+ def join_value(record)
91
+ record.send(join_key)
92
+ end
93
+
94
+ def default_scope
95
+ scope = association_class
96
+ scope = association.scopes.reduce(scope, &:merge)
97
+ scope = association_class.default_scopes.reduce(scope, &:merge)
98
+ scope
99
+ end
100
+
101
+ def belongs_to?
102
+ association.macro == :belongs_to
103
+ end
104
+
105
+ def has_many?
106
+ association.macro == :has_many
107
+ end
108
+
109
+ def association
110
+ @model.reflect_on_association(@association_name)
111
+ end
112
+
113
+ def association_class
114
+ association.klass
115
+ end
116
+
117
+ def primary_key
118
+ @model.primary_key
119
+ end
120
+
121
+ def arel_order(table)
122
+ table[@sort_by].send(@sort_order)
123
+ end
124
+
125
+ def normalize_sort_order(input)
126
+ if input.to_s.casecmp("asc").zero?
127
+ :asc
128
+ else
129
+ :desc
130
+ end
131
+ end
132
+
133
+ def validate!
134
+ raise ArgumentError, "No association #{@association_name} on #{@model}" unless association
135
+ end
136
+ end
137
+ end
138
+ end
@@ -1,21 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hq/graphql/resource/mutation"
3
+ require "hq/graphql/resource/auto_mutation"
4
4
 
5
5
  module HQ
6
6
  module GraphQL
7
7
  module Resource
8
8
  def self.included(base)
9
9
  super
10
- ::HQ::GraphQL.types << base
10
+ ::HQ::GraphQL.resources << base
11
11
  base.include Scalars
12
12
  base.include ::GraphQL::Types
13
13
  base.extend ClassMethods
14
14
  end
15
15
 
16
16
  module ClassMethods
17
+ include AutoMutation
18
+
17
19
  attr_writer :graphql_name, :model_name
18
20
 
21
+ def sort_fields(*fields)
22
+ self.sort_fields_enum = fields
23
+ end
24
+
25
+ def sort_fields_enum
26
+ @sort_fields_enum || ::HQ::GraphQL::Enum::SortBy
27
+ end
28
+
29
+ def sort_fields_enum=(fields)
30
+ @sort_fields_enum ||= Class.new(::HQ::GraphQL::Enum::SortBy).tap do |c|
31
+ c.graphql_name "#{graphql_name}Sort"
32
+ end
33
+
34
+ Array(fields).each do |field|
35
+ @sort_fields_enum.value field.to_s.classify, value: field
36
+ end
37
+ end
38
+
19
39
  def scope(context)
20
40
  scope = model_klass
21
41
  scope = ::HQ::GraphQL.default_scope(scope, context)
@@ -71,144 +91,10 @@ module HQ
71
91
  end
72
92
 
73
93
  def mutations(create: true, copy: true, update: true, destroy: true)
74
- scoped_graphql_name = graphql_name
75
- scoped_model_name = model_name
76
- scoped_self = self
77
-
78
- if create
79
- create_mutation = ::HQ::GraphQL::Resource::Mutation.build(model_name, action: :create, graphql_name: "#{scoped_graphql_name}Create") do
80
- define_method(:resolve) do |**args|
81
- resource = scoped_self.new_record(context)
82
- resource.assign_attributes(args[:attributes].format_nested_attributes)
83
- if resource.save
84
- {
85
- resource: resource,
86
- errors: {},
87
- }
88
- else
89
- {
90
- resource: nil,
91
- errors: errors_from_resource(resource)
92
- }
93
- end
94
- end
95
-
96
- lazy_load do
97
- argument :attributes, ::HQ::GraphQL::Inputs[scoped_model_name], required: true
98
- end
99
- end
100
-
101
- mutation_klasses["create_#{scoped_graphql_name.underscore}"] = create_mutation
102
- end
103
-
104
- if copy
105
- copy_mutation = ::HQ::GraphQL::Resource::Mutation.build(
106
- model_name,
107
- action: :copy,
108
- graphql_name: "#{scoped_graphql_name}Copy",
109
- require_primary_key: true,
110
- nil_klass: true
111
- ) do
112
- define_method(:resolve) do |**args|
113
- resource = scoped_self.find_record(args, context)
114
-
115
- if resource
116
- copy = resource.copy
117
- if copy.save
118
- {
119
- resource: copy,
120
- errors: {},
121
- }
122
- else
123
- {
124
- resource: copy,
125
- errors: errors_from_resource(copy)
126
- }
127
- end
128
- else
129
- {
130
- resource: nil,
131
- errors: { resource: "Unable to find #{scoped_graphql_name}" }
132
- }
133
- end
134
- end
135
- end
136
-
137
- mutation_klasses["copy_#{scoped_graphql_name.underscore}"] = copy_mutation
138
- end
139
-
140
- if update
141
- update_mutation = ::HQ::GraphQL::Resource::Mutation.build(
142
- model_name,
143
- action: :update,
144
- graphql_name: "#{scoped_graphql_name}Update",
145
- require_primary_key: true
146
- ) do
147
- define_method(:resolve) do |**args|
148
- resource = scoped_self.find_record(args, context)
149
-
150
- if resource
151
- resource.assign_attributes(args[:attributes].format_nested_attributes)
152
- if resource.save
153
- {
154
- resource: resource,
155
- errors: {},
156
- }
157
- else
158
- {
159
- resource: nil,
160
- errors: errors_from_resource(resource)
161
- }
162
- end
163
- else
164
- {
165
- resource: nil,
166
- errors: { resource: "Unable to find #{scoped_graphql_name}" }
167
- }
168
- end
169
- end
170
-
171
- lazy_load do
172
- argument :attributes, ::HQ::GraphQL::Inputs[scoped_model_name], required: true
173
- end
174
- end
175
-
176
- mutation_klasses["update_#{scoped_graphql_name.underscore}"] = update_mutation
177
- end
178
-
179
- if destroy
180
- destroy_mutation = ::HQ::GraphQL::Resource::Mutation.build(
181
- model_name,
182
- action: :destroy,
183
- graphql_name: "#{scoped_graphql_name}Destroy",
184
- require_primary_key: true
185
- ) do
186
- define_method(:resolve) do |**attrs|
187
- resource = scoped_self.find_record(attrs, context)
188
-
189
- if resource
190
- if resource.destroy
191
- {
192
- resource: resource,
193
- errors: {},
194
- }
195
- else
196
- {
197
- resource: nil,
198
- errors: errors_from_resource(resource)
199
- }
200
- end
201
- else
202
- {
203
- resource: nil,
204
- errors: { resource: "Unable to find #{scoped_graphql_name}" }
205
- }
206
- end
207
- end
208
- end
209
-
210
- mutation_klasses["destroy_#{scoped_graphql_name.underscore}"] = destroy_mutation
211
- end
94
+ mutation_klasses["create_#{graphql_name.underscore}"] = build_create if create
95
+ mutation_klasses["copy_#{graphql_name.underscore}"] = build_copy if copy
96
+ mutation_klasses["update_#{graphql_name.underscore}"] = build_update if update
97
+ mutation_klasses["destroy_#{graphql_name.underscore}"] = build_destroy if destroy
212
98
  end
213
99
 
214
100
  def query(**options, &block)
@@ -216,10 +102,10 @@ module HQ
216
102
  end
217
103
 
218
104
  def def_root(field_name, is_array: false, null: true, &block)
219
- graphql = self
105
+ resource = self
220
106
  resolver = -> {
221
107
  Class.new(::GraphQL::Schema::Resolver) do
222
- type = is_array ? [graphql.query_klass] : graphql.query_klass
108
+ type = is_array ? [resource.query_klass] : resource.query_klass
223
109
  type type, null: null
224
110
  class_eval(&block) if block
225
111
  end
@@ -229,7 +115,7 @@ module HQ
229
115
  }
230
116
  end
231
117
 
232
- def root_query(find_one: true, find_all: true, pagination: false, per_page_max: 250)
118
+ def root_query(find_one: true, find_all: true, pagination: true, limit_max: 250)
233
119
  field_name = graphql_name.underscore
234
120
  scoped_self = self
235
121
 
@@ -248,18 +134,27 @@ module HQ
248
134
 
249
135
  if find_all
250
136
  def_root field_name.pluralize, is_array: true, null: false do
251
- argument :page, Integer, required: false
252
- argument :per_page, Integer, required: false
137
+ if pagination
138
+ argument :offset, Integer, required: false
139
+ argument :limit, Integer, required: false
140
+ end
141
+ argument :sort_by, scoped_self.sort_fields_enum, required: false
142
+ argument :sort_order, Enum::SortOrder, required: false
253
143
 
254
- define_method(:resolve) do |page: nil, per_page: nil, **_attrs|
144
+ define_method(:resolve) do |limit: nil, offset: nil, sort_by: nil, sort_order: nil, **_attrs|
255
145
  scope = scoped_self.scope(context).all
256
146
 
257
- if pagination || page || per_page
258
- page ||= 0
259
- limit = [per_page_max, *per_page].min
260
- scope = scope.limit(limit).offset(page * limit)
147
+ if pagination || page || limit
148
+ offset = [0, *offset].max
149
+ limit = [[limit_max, *limit].min, 0].max
150
+ scope = scope.limit(limit).offset(offset)
261
151
  end
262
152
 
153
+ sort_by ||= :updated_at
154
+ sort_order ||= :desc
155
+ # There should be no risk for SQL injection since an enum is being used for both sort_by and sort_order
156
+ scope = scope.reorder(sort_by => sort_order)
157
+
263
158
  scope
264
159
  end
265
160
  end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HQ
4
+ module GraphQL
5
+ module Resource
6
+ module AutoMutation
7
+ def build_create
8
+ scoped_self = self
9
+
10
+ build_mutation(action: :create) do
11
+ define_method(:resolve) do |**args|
12
+ resource = scoped_self.new_record(context)
13
+ resource.assign_attributes(args[:attributes].format_nested_attributes)
14
+ if resource.save
15
+ {
16
+ resource: resource,
17
+ errors: {},
18
+ }
19
+ else
20
+ {
21
+ resource: nil,
22
+ errors: errors_from_resource(resource)
23
+ }
24
+ end
25
+ end
26
+
27
+ lazy_load do
28
+ argument :attributes, ::HQ::GraphQL::Inputs[scoped_self.model_name], required: true
29
+ end
30
+ end
31
+ end
32
+
33
+ def build_update
34
+ scoped_self = self
35
+
36
+ build_mutation(action: :update, require_primary_key: true) do
37
+ define_method(:resolve) do |**args|
38
+ resource = scoped_self.find_record(args, context)
39
+
40
+ if resource
41
+ resource.assign_attributes(args[:attributes].format_nested_attributes)
42
+ if resource.save
43
+ {
44
+ resource: resource,
45
+ errors: {},
46
+ }
47
+ else
48
+ {
49
+ resource: nil,
50
+ errors: errors_from_resource(resource)
51
+ }
52
+ end
53
+ else
54
+ {
55
+ resource: nil,
56
+ errors: { resource: "Unable to find #{self.class.graphql_name}" }
57
+ }
58
+ end
59
+ end
60
+
61
+ lazy_load do
62
+ argument :attributes, ::HQ::GraphQL::Inputs[scoped_self.model_name], required: true
63
+ end
64
+ end
65
+ end
66
+
67
+ def build_copy
68
+ scoped_self = self
69
+
70
+ build_mutation(action: :copy, require_primary_key: true, nil_klass: true) do
71
+ define_method(:resolve) do |**args|
72
+ resource = scoped_self.find_record(args, context)
73
+
74
+ if resource
75
+ copy = resource.copy
76
+ if copy.save
77
+ {
78
+ resource: copy,
79
+ errors: {},
80
+ }
81
+ else
82
+ {
83
+ resource: copy,
84
+ errors: errors_from_resource(copy)
85
+ }
86
+ end
87
+ else
88
+ {
89
+ resource: nil,
90
+ errors: { resource: "Unable to find #{self.class.graphql_name}" }
91
+ }
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def build_destroy
98
+ scoped_self = self
99
+
100
+ build_mutation(action: :destroy, require_primary_key: true) do
101
+ define_method(:resolve) do |**attrs|
102
+ resource = scoped_self.find_record(attrs, context)
103
+
104
+ if resource
105
+ if resource.destroy
106
+ {
107
+ resource: resource,
108
+ errors: {},
109
+ }
110
+ else
111
+ {
112
+ resource: nil,
113
+ errors: errors_from_resource(resource)
114
+ }
115
+ end
116
+ else
117
+ {
118
+ resource: nil,
119
+ errors: { resource: "Unable to find #{self.class.graphql_name}" }
120
+ }
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ def build_mutation(action:, require_primary_key: false, nil_klass: false, &block)
127
+ gql_name = "#{graphql_name}#{action.to_s.titleize}"
128
+ scoped_model_name = model_name
129
+ Class.new(::HQ::GraphQL::Mutation) do
130
+ graphql_name gql_name
131
+
132
+ define_method(:ready?) do |*args|
133
+ super(*args) && ::HQ::GraphQL.authorized?(action, scoped_model_name, context)
134
+ end
135
+
136
+ lazy_load do
137
+ field :errors, ::HQ::GraphQL::Types::Object, null: false
138
+ field :resource, ::HQ::GraphQL::Types[scoped_model_name, nil_klass], null: true
139
+ end
140
+
141
+ instance_eval(&block)
142
+
143
+ if require_primary_key
144
+ lazy_load do
145
+ klass = scoped_model_name.constantize
146
+ primary_key = klass.primary_key
147
+ argument primary_key, ::GraphQL::Types::ID, required: true
148
+ end
149
+ end
150
+
151
+ def errors_from_resource(resource)
152
+ resource.errors.to_h.deep_transform_keys { |k| k.to_s.camelize(:lower) }
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -7,7 +7,7 @@ module HQ
7
7
  super
8
8
  base.class_eval do
9
9
  lazy_load do
10
- ::HQ::GraphQL.types.each do |type|
10
+ ::HQ::GraphQL.resources.each do |type|
11
11
  type.mutation_klasses.each do |mutation_name, klass|
12
12
  field mutation_name, mutation: klass
13
13
  end
@@ -64,16 +64,10 @@ module HQ
64
64
 
65
65
  def find_klass(klass_or_string, method)
66
66
  klass = klass_or_string.is_a?(String) ? klass_or_string.constantize : klass_or_string
67
- type = find_type(klass)
68
- type ||= find_type(klass.base_class)
69
- type ||= find_type(klass.superclass)
67
+ resource = ::HQ::GraphQL.lookup_resource(klass)
70
68
 
71
- raise(Error, Error::MISSING_TYPE_MSG % { klass: klass.name }) if !type
72
- type.send(method)
73
- end
74
-
75
- def find_type(klass)
76
- ::HQ::GraphQL.resource_lookup(klass) || ::HQ::GraphQL.types.detect { |t| t.model_klass == klass }
69
+ raise(Error, Error::MISSING_TYPE_MSG % { klass: klass.name }) if !resource
70
+ resource.send(method)
77
71
  end
78
72
  end
79
73
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module HQ
4
4
  module GraphQL
5
- VERSION = "2.0.11"
5
+ VERSION = "2.1.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hq-graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.11
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Jones
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-18 00:00:00.000000000 Z
11
+ date: 2020-06-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -252,13 +252,16 @@ files:
252
252
  - lib/hq/graphql/config.rb
253
253
  - lib/hq/graphql/engine.rb
254
254
  - lib/hq/graphql/enum.rb
255
+ - lib/hq/graphql/enum/sort_by.rb
256
+ - lib/hq/graphql/enum/sort_order.rb
255
257
  - lib/hq/graphql/field.rb
256
258
  - lib/hq/graphql/input_object.rb
257
259
  - lib/hq/graphql/inputs.rb
258
260
  - lib/hq/graphql/mutation.rb
259
261
  - lib/hq/graphql/object.rb
262
+ - lib/hq/graphql/paginated_association_loader.rb
260
263
  - lib/hq/graphql/resource.rb
261
- - lib/hq/graphql/resource/mutation.rb
264
+ - lib/hq/graphql/resource/auto_mutation.rb
262
265
  - lib/hq/graphql/root_mutation.rb
263
266
  - lib/hq/graphql/root_query.rb
264
267
  - lib/hq/graphql/scalars.rb
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module HQ
4
- module GraphQL
5
- module Resource
6
- module Mutation
7
- def self.build(model_name, action:, graphql_name:, require_primary_key: false, nil_klass: false, &block)
8
- Class.new(::HQ::GraphQL::Mutation) do
9
- graphql_name graphql_name
10
-
11
- define_method(:ready?) do |*args|
12
- super(*args) && ::HQ::GraphQL.authorized?(action, model_name, context)
13
- end
14
-
15
- lazy_load do
16
- field :errors, ::HQ::GraphQL::Types::Object, null: false
17
- field :resource, ::HQ::GraphQL::Types[model_name, nil_klass], null: true
18
- end
19
-
20
- instance_eval(&block)
21
-
22
- if require_primary_key
23
- lazy_load do
24
- klass = model_name.constantize
25
- primary_key = klass.primary_key
26
- argument primary_key, ::GraphQL::Types::ID, required: true
27
- end
28
- end
29
-
30
- def errors_from_resource(resource)
31
- resource.errors.to_h.deep_transform_keys { |k| k.to_s.camelize(:lower) }
32
- end
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end