hq-graphql 2.1.1 → 2.1.6
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 +5 -0
- data/lib/hq/graphql/active_record_extensions.rb +1 -1
- data/lib/hq/graphql/config.rb +1 -0
- data/lib/hq/graphql/object.rb +9 -7
- data/lib/hq/graphql/object_association.rb +36 -19
- data/lib/hq/graphql/paginated_association_loader.rb +59 -25
- data/lib/hq/graphql/record_loader.rb +31 -0
- data/lib/hq/graphql/resource.rb +22 -9
- data/lib/hq/graphql/types.rb +8 -4
- data/lib/hq/graphql/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6bec5e8d7faf20cfc45c6420b0f989aee48ca2b5c6f72a3d2a9ff1d4be030530
|
4
|
+
data.tar.gz: 6d71560948e242279e32ec521a86c351e7c2278af78694581f5e1bf27b5807a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c62a8b032a5f43dd27c8a50685739cc6a07d21bab55a7b4a6dc750377080f9a3440c6954a129ef56c1c4bd60a90f4d390efae6937875d949c66a5b26b5ab918b
|
7
|
+
data.tar.gz: 40966a645c6777ee54311a8e8eb3f7df001413fdd91d1bcb21e1928c947299a331e69a18960fba6f0102996559f06c2dc56bdeb7ba611b5707b3f7830e61553d
|
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
|
@@ -73,6 +77,7 @@ require "hq/graphql/input_object"
|
|
73
77
|
require "hq/graphql/mutation"
|
74
78
|
require "hq/graphql/object"
|
75
79
|
require "hq/graphql/paginated_association_loader"
|
80
|
+
require "hq/graphql/record_loader"
|
76
81
|
require "hq/graphql/resource"
|
77
82
|
require "hq/graphql/root_mutation"
|
78
83
|
require "hq/graphql/root_query"
|
data/lib/hq/graphql/config.rb
CHANGED
data/lib/hq/graphql/object.rb
CHANGED
@@ -63,13 +63,12 @@ module HQ
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def field_from_association(association, auto_nil:, internal_association: false, &block)
|
66
|
-
# The PaginationAssociationLoader doesn't support through associations yet
|
67
|
-
return if association.through_reflection? && ::HQ::GraphQL.use_experimental_associations?
|
68
|
-
|
69
66
|
association_klass = association.klass
|
70
|
-
name = association.name
|
71
|
-
|
72
|
-
|
67
|
+
name = association.name.to_s
|
68
|
+
return if fields[name]
|
69
|
+
|
70
|
+
klass = model_klass
|
71
|
+
type = Types[association_klass]
|
73
72
|
case association.macro
|
74
73
|
when :has_many
|
75
74
|
field name, [type], null: false, klass: model_name do
|
@@ -91,7 +90,10 @@ module HQ
|
|
91
90
|
end
|
92
91
|
|
93
92
|
def field_from_column(column, auto_nil:)
|
94
|
-
|
93
|
+
name = column.name
|
94
|
+
return if fields[name]
|
95
|
+
|
96
|
+
field name, Types.type_from_column(column), null: !auto_nil || column.null
|
95
97
|
end
|
96
98
|
|
97
99
|
def association_required?(association)
|
@@ -3,6 +3,33 @@
|
|
3
3
|
module HQ
|
4
4
|
module GraphQL
|
5
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
|
+
|
6
33
|
class ResourceReflection
|
7
34
|
attr_reader :name, :scope, :options, :macro, :block
|
8
35
|
|
@@ -23,27 +50,17 @@ module HQ
|
|
23
50
|
end
|
24
51
|
end
|
25
52
|
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
def belongs_to(name, scope = nil, **options, &block)
|
31
|
-
add_reflection(name, scope, options, :belongs_to, block)
|
32
|
-
end
|
33
|
-
|
34
|
-
def has_many(name, scope = nil, through: nil, **options, &block)
|
35
|
-
raise TypeError, "has_many through is unsupported" if through
|
36
|
-
add_reflection(name, scope, options, :has_many, block)
|
37
|
-
end
|
53
|
+
class UpdatedReflection
|
54
|
+
attr_reader :name, :block
|
38
55
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
56
|
+
def initialize(name, block)
|
57
|
+
@name = name
|
58
|
+
@block = block
|
59
|
+
end
|
44
60
|
|
45
|
-
|
46
|
-
|
61
|
+
def reflection(model_klass)
|
62
|
+
model_klass.reflect_on_association(name)
|
63
|
+
end
|
47
64
|
end
|
48
65
|
end
|
49
66
|
end
|
@@ -23,7 +23,7 @@ module HQ
|
|
23
23
|
@limit = [0, limit].max if limit
|
24
24
|
@offset = [0, offset].max if offset
|
25
25
|
@scope = scope
|
26
|
-
@sort_by = sort_by || :
|
26
|
+
@sort_by = sort_by || :created_at
|
27
27
|
@sort_order = normalize_sort_order(sort_order)
|
28
28
|
|
29
29
|
validate!
|
@@ -39,7 +39,8 @@ module HQ
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def perform(records)
|
42
|
-
|
42
|
+
values = records.map { |r| source_value(r) }
|
43
|
+
scope =
|
43
44
|
if @limit || @offset
|
44
45
|
# If a limit or offset is added, then we need to transform the query
|
45
46
|
# into a lateral join so that we can limit on groups of data.
|
@@ -57,53 +58,70 @@ module HQ
|
|
57
58
|
# > ) a_top ON TRUE
|
58
59
|
# > WHERE addresses.user_id IN ($1, $2, ..., $N)
|
59
60
|
# > ORDER BY a_top.created_at DESC
|
60
|
-
inner_table
|
61
|
-
|
61
|
+
inner_table = association_class.arel_table
|
62
|
+
lateral_join_table = through_reflection? ? through_association.klass.arel_table : inner_table
|
63
|
+
from_table = lateral_join_table.alias("outer")
|
62
64
|
|
63
65
|
inside_scope = default_scope.
|
64
66
|
select(inner_table[::Arel.star]).
|
65
|
-
|
66
|
-
where(inner_table[association_key].eq(association_table[association_key])).
|
67
|
+
where(lateral_join_table[target_join_key].eq(from_table[target_join_key])).
|
67
68
|
reorder(arel_order(inner_table)).
|
68
69
|
limit(@limit).
|
69
70
|
offset(@offset)
|
70
71
|
|
71
|
-
|
72
|
+
if through_reflection?
|
73
|
+
# expose the through_reflection key
|
74
|
+
inside_scope = inside_scope.select(lateral_join_table[target_join_key])
|
75
|
+
end
|
76
|
+
|
77
|
+
lateral_table = ::Arel::Table.new("top")
|
72
78
|
association_class.
|
73
|
-
select(
|
74
|
-
from(
|
75
|
-
|
76
|
-
|
77
|
-
reorder(arel_order(
|
79
|
+
select(lateral_table[::Arel.star]).distinct.
|
80
|
+
from(from_table).
|
81
|
+
where(from_table[target_join_key].in(values)).
|
82
|
+
joins("INNER JOIN LATERAL (#{inside_scope.to_sql}) #{lateral_table.name} ON TRUE").
|
83
|
+
reorder(arel_order(lateral_table))
|
78
84
|
else
|
79
|
-
default_scope.
|
80
|
-
|
81
|
-
|
85
|
+
scope = default_scope.reorder(arel_order(association_class.arel_table))
|
86
|
+
|
87
|
+
if through_reflection?
|
88
|
+
scope.where(through_association.name => { target_join_key => values }).
|
89
|
+
# expose the through_reflection key
|
90
|
+
select(association_class.arel_table[::Arel.star], through_association.klass.arel_table[target_join_key])
|
91
|
+
else
|
92
|
+
scope.where(target_join_key => values)
|
93
|
+
end
|
82
94
|
end
|
83
95
|
|
84
96
|
results = scope.to_a
|
85
97
|
records.each do |record|
|
86
|
-
fulfill(record,
|
98
|
+
fulfill(record, target_value(record, results)) unless fulfilled?(record)
|
87
99
|
end
|
88
100
|
end
|
89
101
|
|
90
102
|
private
|
91
103
|
|
92
|
-
def
|
93
|
-
belongs_to? ? association.
|
104
|
+
def source_join_key
|
105
|
+
belongs_to? ? association.foreign_key : association.association_primary_key
|
94
106
|
end
|
95
107
|
|
96
|
-
def
|
97
|
-
|
98
|
-
results.send(enumerator) { |r| r.send(association_key) == join_value(record) }
|
108
|
+
def source_value(record)
|
109
|
+
record.send(source_join_key)
|
99
110
|
end
|
100
111
|
|
101
|
-
def
|
102
|
-
|
112
|
+
def target_join_key
|
113
|
+
if through_reflection?
|
114
|
+
through_association.foreign_key
|
115
|
+
elsif belongs_to?
|
116
|
+
association.association_primary_key
|
117
|
+
else
|
118
|
+
association.foreign_key
|
119
|
+
end
|
103
120
|
end
|
104
121
|
|
105
|
-
def
|
106
|
-
|
122
|
+
def target_value(record, results)
|
123
|
+
enumerator = has_many? ? :select : :detect
|
124
|
+
results.send(enumerator) { |r| r.send(target_join_key) == source_value(record) }
|
107
125
|
end
|
108
126
|
|
109
127
|
def default_scope
|
@@ -111,6 +129,14 @@ module HQ
|
|
111
129
|
scope = association.scopes.reduce(scope, &:merge)
|
112
130
|
scope = association_class.default_scopes.reduce(scope, &:merge)
|
113
131
|
scope = scope.merge(@scope) if @scope
|
132
|
+
|
133
|
+
if through_reflection?
|
134
|
+
source = association_class.arel_table
|
135
|
+
target = through_association.klass.arel_table
|
136
|
+
join = source.join(target).on(target[association.foreign_key].eq(source[source_join_key]))
|
137
|
+
scope = scope.joins(join.join_sources)
|
138
|
+
end
|
139
|
+
|
114
140
|
scope
|
115
141
|
end
|
116
142
|
|
@@ -122,6 +148,14 @@ module HQ
|
|
122
148
|
association.macro == :has_many
|
123
149
|
end
|
124
150
|
|
151
|
+
def through_association
|
152
|
+
association.through_reflection
|
153
|
+
end
|
154
|
+
|
155
|
+
def through_reflection?
|
156
|
+
association.through_reflection?
|
157
|
+
end
|
158
|
+
|
125
159
|
def association
|
126
160
|
if @internal_association
|
127
161
|
Types[@model].reflect_on_association(@association_name)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HQ
|
4
|
+
module GraphQL
|
5
|
+
class RecordLoader < ::GraphQL::Batch::Loader
|
6
|
+
def initialize(model, column: model.primary_key, where: nil)
|
7
|
+
@model = model
|
8
|
+
@column = column.to_s
|
9
|
+
@column_type = model.type_for_attribute(@column)
|
10
|
+
@where = where
|
11
|
+
end
|
12
|
+
|
13
|
+
def load(key)
|
14
|
+
super(@column_type.cast(key))
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform(keys)
|
18
|
+
query(keys).each { |record| fulfill(record.public_send(@column), record) }
|
19
|
+
keys.each { |key| fulfill(key, nil) unless fulfilled?(key) }
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def query(keys)
|
25
|
+
scope = @model
|
26
|
+
scope = scope.where(@where) if @where
|
27
|
+
scope.where(@column => keys)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/hq/graphql/resource.rb
CHANGED
@@ -59,12 +59,20 @@ module HQ
|
|
59
59
|
@input_klass ||= build_input_object
|
60
60
|
end
|
61
61
|
|
62
|
-
def
|
63
|
-
@
|
64
|
-
end
|
65
|
-
|
66
|
-
def
|
67
|
-
@
|
62
|
+
def nil_query_object
|
63
|
+
@nil_query_object ||= build_graphql_object(name: "#{graphql_name}Copy", auto_nil: false)
|
64
|
+
end
|
65
|
+
|
66
|
+
def query_object
|
67
|
+
@query_object ||= begin
|
68
|
+
if @query_object_options
|
69
|
+
options, block = @query_object_options
|
70
|
+
@query_object_options = nil
|
71
|
+
build_graphql_object(**options, &block)
|
72
|
+
else
|
73
|
+
build_graphql_object
|
74
|
+
end
|
75
|
+
end
|
68
76
|
end
|
69
77
|
|
70
78
|
def sort_fields_enum
|
@@ -89,7 +97,11 @@ module HQ
|
|
89
97
|
end
|
90
98
|
|
91
99
|
def query(**options, &block)
|
92
|
-
@
|
100
|
+
@query_object_options = [options, block]
|
101
|
+
end
|
102
|
+
|
103
|
+
def query_class(klass)
|
104
|
+
@query_class = klass
|
93
105
|
end
|
94
106
|
|
95
107
|
def sort_fields(*fields)
|
@@ -100,7 +112,7 @@ module HQ
|
|
100
112
|
resource = self
|
101
113
|
resolver = -> {
|
102
114
|
Class.new(::GraphQL::Schema::Resolver) do
|
103
|
-
type = is_array ? [resource.
|
115
|
+
type = is_array ? [resource.query_object] : resource.query_object
|
104
116
|
type type, null: null
|
105
117
|
class_eval(&block) if block
|
106
118
|
end
|
@@ -156,7 +168,8 @@ module HQ
|
|
156
168
|
def build_graphql_object(name: graphql_name, **options, &block)
|
157
169
|
scoped_graphql_name = name
|
158
170
|
scoped_model_name = model_name
|
159
|
-
|
171
|
+
object_class = @query_class || ::HQ::GraphQL.default_object_class || ::HQ::GraphQL::Object
|
172
|
+
Class.new(object_class) do
|
160
173
|
graphql_name scoped_graphql_name
|
161
174
|
|
162
175
|
with_model scoped_model_name, **options
|
data/lib/hq/graphql/types.rb
CHANGED
@@ -13,7 +13,7 @@ module HQ
|
|
13
13
|
def self.registry
|
14
14
|
@registry ||= Hash.new do |hash, options|
|
15
15
|
klass, nil_klass = Array(options)
|
16
|
-
hash[
|
16
|
+
hash[options] = nil_klass ? nil_query_object(klass) : klass_for(klass)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -42,6 +42,10 @@ module HQ
|
|
42
42
|
::GraphQL::Types::Float
|
43
43
|
when :boolean
|
44
44
|
::GraphQL::Types::Boolean
|
45
|
+
when :date
|
46
|
+
::GraphQL::Types::ISO8601Date
|
47
|
+
when :datetime
|
48
|
+
::GraphQL::Types::ISO8601DateTime
|
45
49
|
else
|
46
50
|
::GraphQL::Types::String
|
47
51
|
end
|
@@ -57,12 +61,12 @@ module HQ
|
|
57
61
|
class << self
|
58
62
|
private
|
59
63
|
|
60
|
-
def
|
61
|
-
find_klass(klass_or_string, :
|
64
|
+
def nil_query_object(klass_or_string)
|
65
|
+
find_klass(klass_or_string, :nil_query_object)
|
62
66
|
end
|
63
67
|
|
64
68
|
def klass_for(klass_or_string)
|
65
|
-
find_klass(klass_or_string, :
|
69
|
+
find_klass(klass_or_string, :query_object)
|
66
70
|
end
|
67
71
|
|
68
72
|
def find_klass(klass_or_string, method)
|
data/lib/hq/graphql/version.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.1.
|
4
|
+
version: 2.1.6
|
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-
|
11
|
+
date: 2020-07-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -264,6 +264,7 @@ files:
|
|
264
264
|
- lib/hq/graphql/object.rb
|
265
265
|
- lib/hq/graphql/object_association.rb
|
266
266
|
- lib/hq/graphql/paginated_association_loader.rb
|
267
|
+
- lib/hq/graphql/record_loader.rb
|
267
268
|
- lib/hq/graphql/resource.rb
|
268
269
|
- lib/hq/graphql/resource/auto_mutation.rb
|
269
270
|
- lib/hq/graphql/root_mutation.rb
|