hq-graphql 2.0.10 → 2.1.3
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/lib/hq/graphql.rb +17 -7
- data/lib/hq/graphql/active_record_extensions.rb +1 -1
- data/lib/hq/graphql/association_loader.rb +49 -0
- data/lib/hq/graphql/config.rb +2 -0
- data/lib/hq/graphql/enum.rb +2 -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 -10
- 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 +4 -0
- data/lib/hq/graphql/inputs.rb +3 -7
- data/lib/hq/graphql/object.rb +37 -9
- data/lib/hq/graphql/object_association.rb +67 -0
- data/lib/hq/graphql/paginated_association_loader.rb +193 -0
- data/lib/hq/graphql/resource.rb +63 -155
- data/lib/hq/graphql/resource/auto_mutation.rb +163 -0
- data/lib/hq/graphql/root_mutation.rb +1 -1
- data/lib/hq/graphql/types.rb +14 -13
- data/lib/hq/graphql/version.rb +1 -1
- metadata +11 -5
- data/lib/hq/graphql/loaders.rb +0 -3
- data/lib/hq/graphql/loaders/association.rb +0 -51
- data/lib/hq/graphql/resource/mutation.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50ee2af7af4309a1fb549c15b88abdb8bda3454eacfdce5136f1997b814c52fb
|
4
|
+
data.tar.gz: 9dc0b893d4ad8bf8a999cba996224488be9c394e55f83ef979460ac19f96c381
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4940761bdccfd01c867e21f69ab66a5459d8b151024ebd79679906ea62fa6c5d2208ce311a339ed720021c2b43221e51ebb092de8f4bff87a15bc3e3f07f376
|
7
|
+
data.tar.gz: 00a7683630411f065d954132918b3449ef6aec9419de30702dd43bf99ad34fb8dbaf5ee1ae248759f3029006db3863257fe86a99d7b89a7456123c82d52ef7b3
|
data/lib/hq/graphql.rb
CHANGED
@@ -8,6 +8,10 @@ require "hq/graphql/config"
|
|
8
8
|
|
9
9
|
module HQ
|
10
10
|
module GraphQL
|
11
|
+
class << self
|
12
|
+
delegate :default_object_class, to: :config
|
13
|
+
end
|
14
|
+
|
11
15
|
def self.config
|
12
16
|
@config ||= ::HQ::GraphQL::Config.new
|
13
17
|
end
|
@@ -32,14 +36,20 @@ module HQ
|
|
32
36
|
config.extract_class.call(klass)
|
33
37
|
end
|
34
38
|
|
35
|
-
def self.
|
36
|
-
|
39
|
+
def self.lookup_resource(klass)
|
40
|
+
[klass, klass.base_class, klass.superclass].lazy.map do |k|
|
41
|
+
config.resource_lookup.call(k) || resources.detect { |r| r.model_klass == k }
|
42
|
+
end.reject(&:nil?).first
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.use_experimental_associations?
|
46
|
+
!!config.use_experimental_associations
|
37
47
|
end
|
38
48
|
|
39
49
|
def self.reset!
|
40
50
|
@root_queries = nil
|
41
51
|
@enums = nil
|
42
|
-
@
|
52
|
+
@resources = nil
|
43
53
|
::HQ::GraphQL::Inputs.reset!
|
44
54
|
::HQ::GraphQL::Types.reset!
|
45
55
|
end
|
@@ -52,21 +62,21 @@ module HQ
|
|
52
62
|
@enums ||= Set.new
|
53
63
|
end
|
54
64
|
|
55
|
-
def self.
|
56
|
-
@
|
65
|
+
def self.resources
|
66
|
+
@resources ||= Set.new
|
57
67
|
end
|
58
68
|
end
|
59
69
|
end
|
60
70
|
|
61
|
-
require "hq/graphql/
|
71
|
+
require "hq/graphql/association_loader"
|
62
72
|
require "hq/graphql/scalars"
|
63
73
|
require "hq/graphql/comparator"
|
64
74
|
require "hq/graphql/enum"
|
65
75
|
require "hq/graphql/inputs"
|
66
76
|
require "hq/graphql/input_object"
|
67
|
-
require "hq/graphql/loaders"
|
68
77
|
require "hq/graphql/mutation"
|
69
78
|
require "hq/graphql/object"
|
79
|
+
require "hq/graphql/paginated_association_loader"
|
70
80
|
require "hq/graphql/resource"
|
71
81
|
require "hq/graphql/root_mutation"
|
72
82
|
require "hq/graphql/root_query"
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HQ
|
4
|
+
module GraphQL
|
5
|
+
class AssociationLoader < ::GraphQL::Batch::Loader
|
6
|
+
def initialize(model, association_name)
|
7
|
+
@model = model
|
8
|
+
@association_name = association_name
|
9
|
+
validate
|
10
|
+
end
|
11
|
+
|
12
|
+
def load(record)
|
13
|
+
raise TypeError, "#{@model} loader can't load association for #{record.class}" unless record.is_a?(@model)
|
14
|
+
return Promise.resolve(read_association(record)) if association_loaded?(record)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# We want to load the associations on all records, even if they have the same id
|
19
|
+
def cache_key(record)
|
20
|
+
record.object_id
|
21
|
+
end
|
22
|
+
|
23
|
+
def perform(records)
|
24
|
+
preload_association(records)
|
25
|
+
records.each { |record| fulfill(record, read_association(record)) }
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def validate
|
31
|
+
unless @model.reflect_on_association(@association_name)
|
32
|
+
raise ArgumentError, "No association #{@association_name} on #{@model}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def preload_association(records)
|
37
|
+
::ActiveRecord::Associations::Preloader.new.preload(records, @association_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def read_association(record)
|
41
|
+
record.public_send(@association_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def association_loaded?(record)
|
45
|
+
record.association(@association_name).loaded?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/hq/graphql/config.rb
CHANGED
data/lib/hq/graphql/enum.rb
CHANGED
data/lib/hq/graphql/field.rb
CHANGED
@@ -3,13 +3,21 @@
|
|
3
3
|
module HQ
|
4
4
|
module GraphQL
|
5
5
|
class Field < ::GraphQL::Schema::Field
|
6
|
-
attr_reader :authorize_action, :authorize
|
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
|
-
@
|
12
|
+
@klass_or_string = klass
|
13
|
+
end
|
14
|
+
|
15
|
+
def scope(&block)
|
16
|
+
if block
|
17
|
+
@scope = block
|
18
|
+
else
|
19
|
+
@scope
|
20
|
+
end
|
13
21
|
end
|
14
22
|
|
15
23
|
def authorized?(object, ctx)
|
@@ -18,14 +26,8 @@ module HQ
|
|
18
26
|
::HQ::GraphQL.authorize_field(authorize_action, self, object, ctx)
|
19
27
|
end
|
20
28
|
|
21
|
-
def
|
22
|
-
|
23
|
-
Loaders::Association.for(klass.constantize, original_name).load(object.object).then do
|
24
|
-
super
|
25
|
-
end
|
26
|
-
else
|
27
|
-
super
|
28
|
-
end
|
29
|
+
def klass
|
30
|
+
@klass ||= @klass_or_string.is_a?(String) ? @klass_or_string.constantize : @klass_or_string
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hq/graphql/association_loader"
|
4
|
+
|
5
|
+
module HQ
|
6
|
+
module GraphQL
|
7
|
+
module FieldExtension
|
8
|
+
class AssociationLoaderExtension < ::GraphQL::Schema::FieldExtension
|
9
|
+
def resolve(object:, **_kwargs)
|
10
|
+
AssociationLoader.for(options[:klass], field.original_name).load(object.object)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hq/graphql/enum/sort_by"
|
4
|
+
require "hq/graphql/enum/sort_order"
|
5
|
+
|
6
|
+
module HQ
|
7
|
+
module GraphQL
|
8
|
+
module FieldExtension
|
9
|
+
class PaginatedArguments < ::GraphQL::Schema::FieldExtension
|
10
|
+
def apply
|
11
|
+
field.argument :offset, Integer, required: false
|
12
|
+
field.argument :limit, Integer, required: false
|
13
|
+
field.argument :sort_order, Enum::SortOrder, required: false
|
14
|
+
|
15
|
+
resource = ::HQ::GraphQL.lookup_resource(options[:klass])
|
16
|
+
enum = resource ? resource.sort_fields_enum : ::HQ::GraphQL::Enum::SortBy
|
17
|
+
field.argument :sort_by, enum, required: false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hq/graphql/paginated_association_loader"
|
4
|
+
|
5
|
+
module HQ
|
6
|
+
module GraphQL
|
7
|
+
module FieldExtension
|
8
|
+
class PaginatedLoader < ::GraphQL::Schema::FieldExtension
|
9
|
+
def resolve(object:, arguments:, **_options)
|
10
|
+
limit = arguments[:limit]
|
11
|
+
offset = arguments[:offset]
|
12
|
+
sort_by = arguments[:sort_by]
|
13
|
+
sort_order = arguments[:sort_order]
|
14
|
+
scope = field.scope.call(**arguments.except(:limit, :offset, :sort_by, :sort_order)) if field.scope
|
15
|
+
loader = PaginatedAssociationLoader.for(
|
16
|
+
klass,
|
17
|
+
association,
|
18
|
+
internal_association: internal_association,
|
19
|
+
scope: scope,
|
20
|
+
limit: limit,
|
21
|
+
offset: offset,
|
22
|
+
sort_by: sort_by,
|
23
|
+
sort_order: sort_order
|
24
|
+
)
|
25
|
+
|
26
|
+
loader.load(object.object)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def association
|
32
|
+
options[:association]
|
33
|
+
end
|
34
|
+
|
35
|
+
def internal_association
|
36
|
+
options[:internal_association]
|
37
|
+
end
|
38
|
+
|
39
|
+
def klass
|
40
|
+
options[:klass]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/hq/graphql/inputs.rb
CHANGED
@@ -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
|
-
|
27
|
+
resource = ::HQ::GraphQL.lookup_resource(klass)
|
28
28
|
|
29
|
-
raise(Error, Error::MISSING_TYPE_MSG % { klass: klass.name }) if !
|
30
|
-
|
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
|
data/lib/hq/graphql/object.rb
CHANGED
@@ -1,12 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "hq/graphql/active_record_extensions"
|
4
|
+
require "hq/graphql/field"
|
5
|
+
require "hq/graphql/field_extension/association_loader_extension"
|
6
|
+
require "hq/graphql/field_extension/paginated_arguments"
|
7
|
+
require "hq/graphql/field_extension/paginated_loader"
|
8
|
+
require "hq/graphql/object_association"
|
9
|
+
require "hq/graphql/types"
|
10
|
+
|
3
11
|
module HQ
|
4
12
|
module GraphQL
|
5
13
|
class Object < ::GraphQL::Schema::Object
|
6
14
|
include Scalars
|
7
|
-
include
|
15
|
+
include ActiveRecordExtensions
|
16
|
+
extend ObjectAssociation
|
8
17
|
|
9
|
-
field_class
|
18
|
+
field_class Field
|
10
19
|
|
11
20
|
def self.authorize_action(action)
|
12
21
|
self.authorized_action = action
|
@@ -28,8 +37,15 @@ module HQ
|
|
28
37
|
end
|
29
38
|
|
30
39
|
model_associations.each do |association|
|
40
|
+
next if resource_reflections[association.name.to_s]
|
31
41
|
field_from_association(association, auto_nil: auto_nil)
|
32
42
|
end
|
43
|
+
|
44
|
+
resource_reflections.values.each do |resource_reflection|
|
45
|
+
reflection = resource_reflection.reflection(model_klass)
|
46
|
+
next unless reflection
|
47
|
+
field_from_association(reflection, auto_nil: auto_nil, internal_association: true, &resource_reflection.block)
|
48
|
+
end
|
33
49
|
end
|
34
50
|
end
|
35
51
|
|
@@ -46,21 +62,33 @@ module HQ
|
|
46
62
|
@authorized_action ||= :read
|
47
63
|
end
|
48
64
|
|
49
|
-
def field_from_association(association, auto_nil:)
|
50
|
-
|
51
|
-
name
|
65
|
+
def field_from_association(association, auto_nil:, internal_association: false, &block)
|
66
|
+
association_klass = association.klass
|
67
|
+
name = association.name
|
68
|
+
klass = model_klass
|
69
|
+
type = Types[association_klass]
|
52
70
|
case association.macro
|
53
71
|
when :has_many
|
54
|
-
field name, [type], null: false, klass: model_name
|
72
|
+
field name, [type], null: false, klass: model_name do
|
73
|
+
if ::HQ::GraphQL.use_experimental_associations?
|
74
|
+
extension FieldExtension::PaginatedArguments, klass: association_klass
|
75
|
+
extension FieldExtension::PaginatedLoader, klass: klass, association: name, internal_association: internal_association
|
76
|
+
else
|
77
|
+
extension FieldExtension::AssociationLoaderExtension, klass: klass
|
78
|
+
end
|
79
|
+
instance_eval(&block) if block
|
80
|
+
end
|
55
81
|
else
|
56
|
-
field name, type, null: !auto_nil || !association_required?(association), klass: model_name
|
82
|
+
field name, type, null: !auto_nil || !association_required?(association), klass: model_name do
|
83
|
+
extension FieldExtension::AssociationLoaderExtension, klass: klass
|
84
|
+
end
|
57
85
|
end
|
58
|
-
rescue
|
86
|
+
rescue Types::Error
|
59
87
|
nil
|
60
88
|
end
|
61
89
|
|
62
90
|
def field_from_column(column, auto_nil:)
|
63
|
-
field column.name,
|
91
|
+
field column.name, Types.type_from_column(column), null: !auto_nil || column.null
|
64
92
|
end
|
65
93
|
|
66
94
|
def association_required?(association)
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HQ
|
4
|
+
module GraphQL
|
5
|
+
module ObjectAssociation
|
6
|
+
def reflect_on_association(association)
|
7
|
+
resource_reflections[association.to_s]&.reflection(model_klass)
|
8
|
+
end
|
9
|
+
|
10
|
+
def update(name, &block)
|
11
|
+
resource_reflections[name.to_s] = UpdatedReflection.new(name, block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def belongs_to(name, scope = nil, **options, &block)
|
15
|
+
add_reflection(name, scope, options, :belongs_to, block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def has_many(name, scope = nil, through: nil, **options, &block)
|
19
|
+
raise TypeError, "has_many through is unsupported" if through
|
20
|
+
add_reflection(name, scope, options, :has_many, block)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def resource_reflections
|
26
|
+
@resource_reflections ||= {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_reflection(name, scope, options, macro, block)
|
30
|
+
resource_reflections[name.to_s] = ResourceReflection.new(name, scope, options, macro, block)
|
31
|
+
end
|
32
|
+
|
33
|
+
class ResourceReflection
|
34
|
+
attr_reader :name, :scope, :options, :macro, :block
|
35
|
+
|
36
|
+
def initialize(name, scope, options, macro, block)
|
37
|
+
@name = name
|
38
|
+
@scope = scope
|
39
|
+
@options = options
|
40
|
+
@macro = macro
|
41
|
+
@block = block
|
42
|
+
end
|
43
|
+
|
44
|
+
def reflection(model_klass)
|
45
|
+
if macro == :has_many
|
46
|
+
::ActiveRecord::Associations::Builder::HasMany.create_reflection(model_klass, name, scope, options)
|
47
|
+
elsif macro == :belongs_to
|
48
|
+
::ActiveRecord::Associations::Builder::BelongsTo.create_reflection(model_klass, name, scope, options)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class UpdatedReflection
|
54
|
+
attr_reader :name, :block
|
55
|
+
|
56
|
+
def initialize(name, block)
|
57
|
+
@name = name
|
58
|
+
@block = block
|
59
|
+
end
|
60
|
+
|
61
|
+
def reflection(model_klass)
|
62
|
+
model_klass.reflect_on_association(name)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|