hq-graphql 2.0.11 → 2.1.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 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