hq-graphql 2.1.2 → 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 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