hq-graphql 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/hq/graphql/field/association_loader.rb +27 -0
- data/lib/hq/graphql/field.rb +4 -0
- data/lib/hq/graphql/loaders/association.rb +52 -0
- data/lib/hq/graphql/loaders.rb +4 -0
- data/lib/hq/graphql/object.rb +17 -8
- data/lib/hq/graphql/resource/mutation.rb +2 -2
- data/lib/hq/graphql/resource.rb +12 -3
- data/lib/hq/graphql/scalars.rb +1 -1
- data/lib/hq/graphql/types.rb +15 -6
- data/lib/hq/graphql/version.rb +1 -1
- data/lib/hq/graphql.rb +3 -0
- data/lib/hq-graphql.rb +1 -1
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2508bf165d504937a9ea21ecc4b463e98e75c4e0d1ae12156df59ed649e4c2d
|
4
|
+
data.tar.gz: 07c40cac0b734c307953864ba415700b0a5dad4769d48e168361449cbe3e3a8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c7d4b1e507b25c0288a55387c183079e2dfbe8fec512a15c09d47f1352ed14655dd2507f86f996848a7bf4513969d59fa16f6df8b873ce74488867b31a9832e
|
7
|
+
data.tar.gz: 86c8781c2e074dc2d6882144d8604f33d11cd943f1e3effe0949c5efb8ee26ee9b687c697e2e30e146a7651b41f31252b7ff7bfeb237ff331a917a8aba02e798
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module HQ
|
5
|
+
module GraphQL
|
6
|
+
module Field
|
7
|
+
class AssociationLoader < ::GraphQL::Schema::Field
|
8
|
+
attr_reader :loader_klass
|
9
|
+
|
10
|
+
def initialize(*args, loader_klass: nil, **options, &block)
|
11
|
+
super(*args, **options, &block)
|
12
|
+
@loader_klass = loader_klass
|
13
|
+
end
|
14
|
+
|
15
|
+
def resolve_field(object, args, ctx)
|
16
|
+
if loader_klass.present? && !!::GraphQL::Batch::Executor.current && object.object
|
17
|
+
Loaders::Association.for(loader_klass.constantize, original_name).load(object.object).then do
|
18
|
+
super
|
19
|
+
end
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module HQ
|
5
|
+
module GraphQL
|
6
|
+
module Loaders
|
7
|
+
class Association < ::GraphQL::Batch::Loader
|
8
|
+
def initialize(model, association_name)
|
9
|
+
@model = model
|
10
|
+
@association_name = association_name
|
11
|
+
validate
|
12
|
+
end
|
13
|
+
|
14
|
+
def load(record)
|
15
|
+
raise TypeError, "#{@model} loader can't load association for #{record.class}" unless record.is_a?(@model)
|
16
|
+
return Promise.resolve(read_association(record)) if association_loaded?(record)
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
# We want to load the associations on all records, even if they have the same id
|
21
|
+
def cache_key(record)
|
22
|
+
record.object_id
|
23
|
+
end
|
24
|
+
|
25
|
+
def perform(records)
|
26
|
+
preload_association(records)
|
27
|
+
records.each { |record| fulfill(record, read_association(record)) }
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def validate
|
33
|
+
unless @model.reflect_on_association(@association_name)
|
34
|
+
raise ArgumentError, "No association #{@association_name} on #{@model}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def preload_association(records)
|
39
|
+
::ActiveRecord::Associations::Preloader.new.preload(records, @association_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
def read_association(record)
|
43
|
+
record.public_send(@association_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def association_loaded?(record)
|
47
|
+
record.association(@association_name).loaded?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/hq/graphql/object.rb
CHANGED
@@ -7,18 +7,20 @@ module HQ
|
|
7
7
|
include Scalars
|
8
8
|
include ::HQ::GraphQL::ActiveRecordExtensions
|
9
9
|
|
10
|
-
|
10
|
+
field_class ::HQ::GraphQL::Field::AssociationLoader
|
11
|
+
|
12
|
+
def self.with_model(model_name, attributes: true, associations: true, auto_nil: true)
|
11
13
|
self.model_name = model_name
|
12
14
|
self.auto_load_attributes = attributes
|
13
15
|
self.auto_load_associations = associations
|
14
16
|
|
15
17
|
lazy_load do
|
16
18
|
model_columns.each do |column|
|
17
|
-
field_from_column(column)
|
19
|
+
field_from_column(column, auto_nil: auto_nil)
|
18
20
|
end
|
19
21
|
|
20
22
|
model_associations.each do |association|
|
21
|
-
field_from_association
|
23
|
+
field_from_association(association, auto_nil: auto_nil)
|
22
24
|
end
|
23
25
|
end
|
24
26
|
end
|
@@ -31,21 +33,28 @@ module HQ
|
|
31
33
|
class << self
|
32
34
|
private
|
33
35
|
|
34
|
-
def field_from_association(association)
|
36
|
+
def field_from_association(association, auto_nil:)
|
35
37
|
type = ::HQ::GraphQL::Types[association.klass]
|
36
38
|
name = association.name
|
37
39
|
case association.macro
|
38
40
|
when :has_many
|
39
|
-
field name, [type], null: false
|
41
|
+
field name, [type], null: false, loader_klass: model_name
|
40
42
|
else
|
41
|
-
field name, type, null:
|
43
|
+
field name, type, null: !auto_nil || !association_required?(association), loader_klass: model_name
|
42
44
|
end
|
43
45
|
rescue ::HQ::GraphQL::Types::Error
|
44
46
|
nil
|
45
47
|
end
|
46
48
|
|
47
|
-
def field_from_column(column)
|
48
|
-
field column.name, ::HQ::GraphQL::Types.type_from_column(column), null:
|
49
|
+
def field_from_column(column, auto_nil:)
|
50
|
+
field column.name, ::HQ::GraphQL::Types.type_from_column(column), null: !auto_nil || column.null
|
51
|
+
end
|
52
|
+
|
53
|
+
def association_required?(association)
|
54
|
+
!association.options[:optional] || model_klass.validators.any? do |validation|
|
55
|
+
next unless validation.class == ActiveRecord::Validations::PresenceValidator
|
56
|
+
validation.attributes.any? { |a| a.to_s == association.name.to_s }
|
57
|
+
end
|
49
58
|
end
|
50
59
|
end
|
51
60
|
end
|
@@ -5,13 +5,13 @@ module HQ
|
|
5
5
|
module GraphQL
|
6
6
|
module Resource
|
7
7
|
module Mutation
|
8
|
-
def self.build(model_name, graphql_name:, require_primary_key: false, &block)
|
8
|
+
def self.build(model_name, graphql_name:, require_primary_key: false, show_copy: false, &block)
|
9
9
|
Class.new(::HQ::GraphQL::Mutation) do
|
10
10
|
graphql_name graphql_name
|
11
11
|
|
12
12
|
lazy_load do
|
13
13
|
field :errors, ::HQ::GraphQL::Types::Object, null: false
|
14
|
-
field :resource, ::HQ::GraphQL::Types[model_name], null: true
|
14
|
+
field :resource, ::HQ::GraphQL::Types[model_name, show_copy], null: true
|
15
15
|
end
|
16
16
|
|
17
17
|
instance_eval(&block)
|
data/lib/hq/graphql/resource.rb
CHANGED
@@ -53,6 +53,10 @@ module HQ
|
|
53
53
|
@input_klass ||= build_input_object
|
54
54
|
end
|
55
55
|
|
56
|
+
def input_result_klass
|
57
|
+
@input_result_klass ||= build_graphql_object(name: "#{graphql_name}Copy", auto_nil: false)
|
58
|
+
end
|
59
|
+
|
56
60
|
def query_klass
|
57
61
|
@query_klass ||= build_graphql_object
|
58
62
|
end
|
@@ -67,6 +71,10 @@ module HQ
|
|
67
71
|
@input_klass = build_input_object(**options, &block)
|
68
72
|
end
|
69
73
|
|
74
|
+
def input_result(**options, &block)
|
75
|
+
@input_result_klass = build_graphql_object(name: "#{graphql_name}Copy", **options, auto_nil: false, &block)
|
76
|
+
end
|
77
|
+
|
70
78
|
def mutations(create: true, copy: true, update: true, destroy: true)
|
71
79
|
scoped_graphql_name = graphql_name
|
72
80
|
scoped_model_name = model_name
|
@@ -102,7 +110,8 @@ module HQ
|
|
102
110
|
copy_mutation = ::HQ::GraphQL::Resource::Mutation.build(
|
103
111
|
model_name,
|
104
112
|
graphql_name: "#{scoped_graphql_name}Copy",
|
105
|
-
require_primary_key: true
|
113
|
+
require_primary_key: true,
|
114
|
+
show_copy: true
|
106
115
|
) do
|
107
116
|
define_method(:resolve) do |**args|
|
108
117
|
resource = scoped_self.find_record(args, context)
|
@@ -251,8 +260,8 @@ module HQ
|
|
251
260
|
|
252
261
|
private
|
253
262
|
|
254
|
-
def build_graphql_object(**options, &block)
|
255
|
-
scoped_graphql_name =
|
263
|
+
def build_graphql_object(name: graphql_name, **options, &block)
|
264
|
+
scoped_graphql_name = name
|
256
265
|
scoped_model_name = model_name
|
257
266
|
Class.new(::HQ::GraphQL::Object) do
|
258
267
|
graphql_name scoped_graphql_name
|
data/lib/hq/graphql/scalars.rb
CHANGED
data/lib/hq/graphql/types.rb
CHANGED
@@ -8,11 +8,12 @@ module HQ
|
|
8
8
|
MISSING_TYPE_MSG = "The GraphQL type for `%{klass}` is missing."
|
9
9
|
end
|
10
10
|
|
11
|
-
def self.[](key)
|
12
|
-
@types ||= Hash.new do |hash,
|
13
|
-
|
11
|
+
def self.[](key, *args)
|
12
|
+
@types ||= Hash.new do |hash, options|
|
13
|
+
klass, show_copy = Array(options)
|
14
|
+
hash[klass] = show_copy ? copy_klass(klass) : klass_for(klass)
|
14
15
|
end
|
15
|
-
@types[key]
|
16
|
+
@types[[key, *args]]
|
16
17
|
end
|
17
18
|
|
18
19
|
def self.type_from_column(column)
|
@@ -43,10 +44,18 @@ module HQ
|
|
43
44
|
class << self
|
44
45
|
private
|
45
46
|
|
47
|
+
def copy_klass(klass_or_string)
|
48
|
+
find_klass(klass_or_string, :input_result_klass)
|
49
|
+
end
|
50
|
+
|
46
51
|
def klass_for(klass_or_string)
|
52
|
+
find_klass(klass_or_string, :query_klass)
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_klass(klass_or_string, method)
|
47
56
|
klass = klass_or_string.is_a?(String) ? klass_or_string.constantize : klass_or_string
|
48
|
-
::HQ::GraphQL.types.detect { |t| t.model_klass == klass }&.
|
49
|
-
::HQ::GraphQL.types.detect { |t| t.model_klass == klass.base_class }&.
|
57
|
+
::HQ::GraphQL.types.detect { |t| t.model_klass == klass }&.send(method) ||
|
58
|
+
::HQ::GraphQL.types.detect { |t| t.model_klass == klass.base_class }&.send(method) ||
|
50
59
|
raise(Error, Error::MISSING_TYPE_MSG % { klass: klass.name })
|
51
60
|
end
|
52
61
|
end
|
data/lib/hq/graphql/version.rb
CHANGED
data/lib/hq/graphql.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require "rails"
|
5
5
|
require "graphql"
|
6
|
+
require "graphql/batch"
|
6
7
|
require "sorbet-runtime"
|
7
8
|
require "hq/graphql/config"
|
8
9
|
|
@@ -49,8 +50,10 @@ end
|
|
49
50
|
require "hq/graphql/active_record_extensions"
|
50
51
|
require "hq/graphql/scalars"
|
51
52
|
|
53
|
+
require "hq/graphql/field"
|
52
54
|
require "hq/graphql/inputs"
|
53
55
|
require "hq/graphql/input_object"
|
56
|
+
require "hq/graphql/loaders"
|
54
57
|
require "hq/graphql/mutation"
|
55
58
|
require "hq/graphql/object"
|
56
59
|
require "hq/graphql/resource"
|
data/lib/hq-graphql.rb
CHANGED
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.
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Danny Jones
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -44,6 +44,20 @@ dependencies:
|
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
46
|
version: 1.9.6
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: graphql-batch
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.4'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0.4'
|
47
61
|
- !ruby/object:Gem::Dependency
|
48
62
|
name: pg
|
49
63
|
requirement: !ruby/object:Gem::Requirement
|
@@ -241,8 +255,12 @@ files:
|
|
241
255
|
- lib/hq/graphql/active_record_extensions.rb
|
242
256
|
- lib/hq/graphql/config.rb
|
243
257
|
- lib/hq/graphql/engine.rb
|
258
|
+
- lib/hq/graphql/field.rb
|
259
|
+
- lib/hq/graphql/field/association_loader.rb
|
244
260
|
- lib/hq/graphql/input_object.rb
|
245
261
|
- lib/hq/graphql/inputs.rb
|
262
|
+
- lib/hq/graphql/loaders.rb
|
263
|
+
- lib/hq/graphql/loaders/association.rb
|
246
264
|
- lib/hq/graphql/mutation.rb
|
247
265
|
- lib/hq/graphql/object.rb
|
248
266
|
- lib/hq/graphql/resource.rb
|