hq-graphql 2.1.2 → 2.1.3

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: 8851537c95e46beea01cd929ec57a5c29a0649eeb059e7f35b5bef1fe71f6da3
4
- data.tar.gz: 31008afafa861554a0327285681191a4107c83faf7d3c790f402fe88056d2ca6
3
+ metadata.gz: 50ee2af7af4309a1fb549c15b88abdb8bda3454eacfdce5136f1997b814c52fb
4
+ data.tar.gz: 9dc0b893d4ad8bf8a999cba996224488be9c394e55f83ef979460ac19f96c381
5
5
  SHA512:
6
- metadata.gz: f63e936bed969d901e14265ac4fe284d4ef18838cf27c6f4c5a2575d6b0c07e3e4d533e0ea2e8a402777351b98b3d2c0fd07b021781bfb9e618518a0b60d7988
7
- data.tar.gz: 76b08ade067128ab0a4d96449751481413c8a6e714ae9d69c73817a5692468dee0f279d3c13456f504f145ee6f713418714b258b23540193eed48b4d0278e3e8
6
+ metadata.gz: f4940761bdccfd01c867e21f69ab66a5459d8b151024ebd79679906ea62fa6c5d2208ce311a339ed720021c2b43221e51ebb092de8f4bff87a15bc3e3f07f376
7
+ data.tar.gz: 00a7683630411f065d954132918b3449ef6aec9419de30702dd43bf99ad34fb8dbaf5ee1ae248759f3029006db3863257fe86a99d7b89a7456123c82d52ef7b3
@@ -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
@@ -27,7 +27,7 @@ module HQ
27
27
  end
28
28
 
29
29
  def lazy_load!
30
- lazy_load.map(&:call)
30
+ lazy_load.each(&:call)
31
31
  @lazy_load = []
32
32
  end
33
33
 
@@ -5,6 +5,7 @@ module HQ
5
5
  class Config < Struct.new(
6
6
  :authorize,
7
7
  :authorize_field,
8
+ :default_object_class,
8
9
  :default_scope,
9
10
  :extract_class,
10
11
  :resource_lookup,
@@ -63,9 +63,6 @@ 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
67
  name = association.name
71
68
  klass = model_klass
@@ -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
- def reflect_on_association(association)
27
- resource_reflections[association.to_s]&.reflection(model_klass)
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
- private
40
-
41
- def resource_reflections
42
- @resource_reflections ||= {}
43
- end
56
+ def initialize(name, block)
57
+ @name = name
58
+ @block = block
59
+ end
44
60
 
45
- def add_reflection(name, scope, options, macro, block)
46
- resource_reflections[name.to_s] = ResourceReflection.new(name, scope, options, macro, block)
61
+ def reflection(model_klass)
62
+ model_klass.reflect_on_association(name)
63
+ end
47
64
  end
48
65
  end
49
66
  end
@@ -39,7 +39,8 @@ module HQ
39
39
  end
40
40
 
41
41
  def perform(records)
42
- scope =
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,71 @@ 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 = association_class.arel_table
61
- association_table = inner_table.alias("outer")
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
67
  from(inner_table).
66
- where(inner_table[association_key].eq(association_table[association_key])).
68
+ where(lateral_join_table[target_join_key].eq(from_table[target_join_key])).
67
69
  reorder(arel_order(inner_table)).
68
70
  limit(@limit).
69
71
  offset(@offset)
70
72
 
71
- outside_table = ::Arel::Table.new("top")
73
+ if through_reflection?
74
+ # expose the through_reflection key
75
+ inside_scope = inside_scope.select(lateral_join_table[target_join_key])
76
+ end
77
+
78
+ lateral_table = ::Arel::Table.new("top")
72
79
  association_class.
73
- select(outside_table[::Arel.star]).distinct.
74
- from(association_table).
75
- joins("INNER JOIN LATERAL (#{inside_scope.to_sql}) #{outside_table.name} ON TRUE").
76
- where(association_table[association_key].in(records.map { |r| join_value(r) })).
77
- reorder(arel_order(outside_table))
80
+ select(lateral_table[::Arel.star]).distinct.
81
+ from(from_table).
82
+ where(from_table[target_join_key].in(values)).
83
+ joins("INNER JOIN LATERAL (#{inside_scope.to_sql}) #{lateral_table.name} ON TRUE").
84
+ reorder(arel_order(lateral_table))
78
85
  else
79
- default_scope.
80
- reorder(arel_order(association_class.arel_table)).
81
- where(association_key => records.map { |r| join_value(r) })
86
+ scope = default_scope.reorder(arel_order(association_class.arel_table))
87
+
88
+ if through_reflection?
89
+ scope.where(through_association.name => { target_join_key => values }).
90
+ # expose the through_reflection key
91
+ select(association_class.arel_table[::Arel.star], through_association.klass.arel_table[target_join_key])
92
+ else
93
+ scope.where(target_join_key => values)
94
+ end
82
95
  end
83
96
 
84
97
  results = scope.to_a
85
98
  records.each do |record|
86
- fulfill(record, association_value(record, results)) unless fulfilled?(record)
99
+ fulfill(record, target_value(record, results)) unless fulfilled?(record)
87
100
  end
88
101
  end
89
102
 
90
103
  private
91
104
 
92
- def association_key
93
- belongs_to? ? association.association_primary_key : association.foreign_key
105
+ def source_join_key
106
+ belongs_to? ? association.foreign_key : association.association_primary_key
94
107
  end
95
108
 
96
- def association_value(record, results)
97
- enumerator = has_many? ? :select : :detect
98
- results.send(enumerator) { |r| r.send(association_key) == join_value(record) }
109
+ def source_value(record)
110
+ record.send(source_join_key)
99
111
  end
100
112
 
101
- def join_key
102
- belongs_to? ? association.foreign_key : association.association_primary_key
113
+ def target_join_key
114
+ if through_reflection?
115
+ through_association.foreign_key
116
+ elsif belongs_to?
117
+ association.association_primary_key
118
+ else
119
+ association.foreign_key
120
+ end
103
121
  end
104
122
 
105
- def join_value(record)
106
- record.send(join_key)
123
+ def target_value(record, results)
124
+ enumerator = has_many? ? :select : :detect
125
+ results.send(enumerator) { |r| r.send(target_join_key) == source_value(record) }
107
126
  end
108
127
 
109
128
  def default_scope
@@ -111,6 +130,14 @@ module HQ
111
130
  scope = association.scopes.reduce(scope, &:merge)
112
131
  scope = association_class.default_scopes.reduce(scope, &:merge)
113
132
  scope = scope.merge(@scope) if @scope
133
+
134
+ if through_reflection?
135
+ source = association_class.arel_table
136
+ target = through_association.klass.arel_table
137
+ join = source.join(target).on(target[association.foreign_key].eq(source[source_join_key]))
138
+ scope = scope.joins(join.join_sources)
139
+ end
140
+
114
141
  scope
115
142
  end
116
143
 
@@ -122,6 +149,14 @@ module HQ
122
149
  association.macro == :has_many
123
150
  end
124
151
 
152
+ def through_association
153
+ association.through_reflection
154
+ end
155
+
156
+ def through_reflection?
157
+ association.through_reflection?
158
+ end
159
+
125
160
  def association
126
161
  if @internal_association
127
162
  Types[@model].reflect_on_association(@association_name)
@@ -59,12 +59,20 @@ module HQ
59
59
  @input_klass ||= build_input_object
60
60
  end
61
61
 
62
- def nil_query_klass
63
- @nil_query_klass ||= build_graphql_object(name: "#{graphql_name}Copy", auto_nil: false)
64
- end
65
-
66
- def query_klass
67
- @query_klass ||= build_graphql_object
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
- @query_klass = build_graphql_object(**options, &block)
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.query_klass] : resource.query_klass
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
- Class.new(::HQ::GraphQL::Object) do
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
@@ -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[klass] = nil_klass ? nil_query_klass(klass) : klass_for(klass)
16
+ hash[options] = nil_klass ? nil_query_object(klass) : klass_for(klass)
17
17
  end
18
18
  end
19
19
 
@@ -61,12 +61,12 @@ module HQ
61
61
  class << self
62
62
  private
63
63
 
64
- def nil_query_klass(klass_or_string)
65
- find_klass(klass_or_string, :nil_query_klass)
64
+ def nil_query_object(klass_or_string)
65
+ find_klass(klass_or_string, :nil_query_object)
66
66
  end
67
67
 
68
68
  def klass_for(klass_or_string)
69
- find_klass(klass_or_string, :query_klass)
69
+ find_klass(klass_or_string, :query_object)
70
70
  end
71
71
 
72
72
  def find_klass(klass_or_string, method)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module HQ
4
4
  module GraphQL
5
- VERSION = "2.1.2"
5
+ VERSION = "2.1.3"
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.1.2
4
+ version: 2.1.3
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-06-23 00:00:00.000000000 Z
11
+ date: 2020-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails