graphql-groups 0.1.4 → 0.2.0

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: bc6f31b4fca864cefe1967ddd17af2a65a782f53378f6b4c3bc5d7fb76a7819a
4
- data.tar.gz: e64200c1a65480d32cdfb2826615e3ce8d26ae6fa61677e17a95a187c1b2063c
3
+ metadata.gz: d719d15bde5e54f543072d6b188540e38ef8eb1635c156af4f09a1b769fe59b0
4
+ data.tar.gz: 6e8b3a42ff5605c0a4fd99f4581982d4e52860cc51776a354881302c9eabd408
5
5
  SHA512:
6
- metadata.gz: 697fab7612389337ab019a3820cffbf91546ac5ddf026903448b6f5eb6815f0c468225aebe10d3febcee88dcfd174903fa487ae6f83361ed91ec605c43b0e94a
7
- data.tar.gz: 4c99e4cbe13c9fa4e80a7a8c7b0958c2127675ba1e94f501b280160f8b231eda19f1ffaf9dcdfbcb0304d3721d66d416833696c8cf2074cf1092b15010766867
6
+ metadata.gz: c99e16857d3c8450469a3e1ff18ac6ec38e4921108b689413a34b0bccf96f4682ab9827678129d6938188fd956fc93bf95ddab2b00490a630fbb6a48bc803349
7
+ data.tar.gz: 6f0464eff3da15b38bdab1ff27dda8e0131fa72936b567fdd1ad20a5e9fc7e2a390a05ef07afaa2e169dbd4c382cb41904e442f2ac41442df45f174fb045a79c
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- graphql-groups (0.1.4)
4
+ graphql-groups (0.2.0)
5
5
  graphql (~> 1, > 1.9)
6
6
 
7
7
  GEM
@@ -36,6 +36,8 @@ GEM
36
36
  http (> 0.8, < 3.0)
37
37
  multi_json (~> 1)
38
38
  graphql (1.11.1)
39
+ groupdate (5.2.1)
40
+ activesupport (>= 5)
39
41
  gruff (0.10.0)
40
42
  histogram
41
43
  rmagick
@@ -114,6 +116,7 @@ DEPENDENCIES
114
116
  database_cleaner-active_record (~> 1.8)
115
117
  gqli (~> 1.0)
116
118
  graphql-groups!
119
+ groupdate (~> 5.2.1)
117
120
  gruff (~> 0.10)
118
121
  rake (~> 13.0)
119
122
  rspec (~> 3.0)
data/README.md CHANGED
@@ -269,12 +269,11 @@ While it is possible to add grouping to your GraphQL schema by using `group_by`
269
269
 
270
270
  The benchmark queries the author count grouped by name, using an increasing number of authors. While the in-memory approach of grouping works well for a small number of records, it is outperformed quickly as that number increases.
271
271
 
272
- Benchmarks are generated using [benchmark-ips](https://github.com/evanphx/benchmark-ips). The benchmark script used to generate the report be found [here](./benchmark/benchmark.rb)
272
+ Benchmarks can be generated by running `rake benchmark`. The benchmark script used to generate the report be found [here](./benchmark/benchmark.rb)
273
273
 
274
274
  ## Limitations and Known Issues
275
275
 
276
- *This gem is in early development!* There are a number of issues that are still being addressed. There is no guarantee
277
- that this libraries API will not change fundamentally from one release to the next. Please refer to the [issue tracker](https://github.com/hschne/graphql-groups/issues) for a list of known issues.
276
+ Please refer to the [issue tracker](https://github.com/hschne/graphql-groups/issues) for a list of known issues.
278
277
 
279
278
  ## Credits
280
279
 
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.add_development_dependency 'bundler', '~> 2.0'
37
37
  spec.add_development_dependency 'database_cleaner-active_record', '~> 1.8'
38
38
  spec.add_development_dependency 'gqli', '~> 1.0'
39
+ spec.add_development_dependency 'groupdate', '~> 5.2.1'
39
40
  spec.add_development_dependency 'gruff', '~> 0.10'
40
41
  spec.add_development_dependency 'rake', '~> 13.0'
41
42
  spec.add_development_dependency 'rspec', '~> 3.0'
@@ -4,6 +4,7 @@ require 'graphql/groups/version'
4
4
 
5
5
  require 'graphql'
6
6
 
7
+ require 'graphql/groups/utils'
7
8
  require 'graphql/groups/group_type_registry'
8
9
  require 'graphql/groups/schema/group_field'
9
10
  require 'graphql/groups/schema/aggregate_field'
@@ -15,9 +16,11 @@ require 'graphql/groups/has_groups'
15
16
  require 'graphql/groups/schema/group_result_type'
16
17
  require 'graphql/groups/schema/group_type'
17
18
 
18
- require 'graphql/groups/lookahead_parser'
19
+ require 'graphql/groups/query_result'
20
+ require 'graphql/groups/pending_query'
21
+ require 'graphql/groups/query_builder_context'
22
+ require 'graphql/groups/query_builder'
19
23
  require 'graphql/groups/result_transformer'
20
- require 'graphql/groups/executor'
21
24
 
22
25
 
23
26
  module GraphQL
@@ -31,10 +34,9 @@ module GraphQL
31
34
  field name, type, extras: [:lookahead], null: false, **options
32
35
 
33
36
  define_method name do |lookahead: nil|
34
- execution_plan = GraphQL::Groups::LookaheadParser.parse(lookahead, context)
35
- base_query = type.authorized_new(object, context).scope
36
- results = Executor.call(base_query, execution_plan)
37
- GraphQL::Groups::ResultTransformer.new.run(results)
37
+ pending_queries = QueryBuilder.parse(lookahead, object, context)
38
+ query_results = pending_queries.map(&:execute)
39
+ GraphQL::Groups::ResultTransformer.new.run(query_results)
38
40
  end
39
41
  end
40
42
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'singleton'
2
4
 
3
5
  module GraphQL
@@ -11,6 +13,10 @@ module GraphQL
11
13
  @types = {}
12
14
  end
13
15
 
16
+ def clear
17
+ @types = {}
18
+ end
19
+
14
20
  def register(type, derived)
15
21
  types[type] = derived
16
22
  end
@@ -11,7 +11,6 @@ module GraphQL
11
11
  def aggregate(name, *_, **options, &block)
12
12
  aggregate_type = aggregate_type(name)
13
13
 
14
- # TODO: Handle method name conflicts, or no query method found error
15
14
  resolve_method = "resolve_#{name}".to_sym
16
15
  query_method = options[:query_method] || name
17
16
  field = aggregate_field name, aggregate_type,
@@ -21,11 +20,8 @@ module GraphQL
21
20
  **options, &block
22
21
  aggregate_type.add_fields(field.own_attributes)
23
22
 
24
- # TODO: Avoid overwriting existing method
25
- define_method query_method do |**kwargs|
26
- scope = kwargs[:scope]
27
- attribute = kwargs[:attribute]
28
- scope.public_send(name, attribute)
23
+ define_method query_method do |scope:, **kwargs|
24
+ scope.public_send(name, **kwargs)
29
25
  end
30
26
 
31
27
  define_method resolve_method do
@@ -42,7 +38,6 @@ module GraphQL
42
38
  private
43
39
 
44
40
  def aggregate_type(name)
45
- # TODO: Handle no aggregate type found
46
41
  name = "#{name}AggregateType".upcase_first
47
42
  own_aggregate_types[name] ||= Class.new(Schema::AggregateType) do
48
43
  graphql_name name
@@ -17,28 +17,27 @@ module GraphQL
17
17
  module ClassMethods
18
18
  attr_reader :class_scope
19
19
 
20
- # TODO: Error if there are no groupings defined
21
20
  def by(name, **options, &block)
22
21
  query_method = options[:query_method] || name
23
22
  resolver_method = "resolve_#{query_method}".to_sym
24
23
  group_field name, [own_result_type],
25
- null: false,
26
- resolver_method: resolver_method,
27
24
  query_method: query_method,
25
+ null: true,
26
+ resolver_method: resolver_method,
27
+
28
28
  **options, &block
29
29
 
30
30
  define_method query_method do |**kwargs|
31
31
  kwargs[:scope].group(name)
32
32
  end
33
33
 
34
- # TODO: Warn / Disallow overwriting these resolver methods
35
34
  define_method resolver_method do |**_|
36
35
  group[name]
37
36
  end
38
37
  end
39
38
 
40
39
  def group_field(*args, **kwargs, &block)
41
- field_defn = Schema::GroupField.from_options(*args, owner: self, **kwargs, &block)
40
+ field_defn = field_class.from_options(*args, owner: self, **kwargs, &block)
42
41
  add_field(field_defn)
43
42
  field_defn
44
43
  end
@@ -65,7 +64,21 @@ module GraphQL
65
64
  field :group_by, own_group_type, null: false, camelize: true
66
65
 
67
66
  def group_by
68
- group_result[1][:nested]
67
+ group_result[1][:group_by]
68
+ end
69
+ end)
70
+ end
71
+
72
+ def own_field_type
73
+ type = "#{name}Field"
74
+ base_field_type = field_class
75
+ registry = GraphQL::Groups::GroupTypeRegistry.instance
76
+ registry.get(type) || registry.register(type, Class.new(base_field_type) do
77
+ attr_reader :query_method
78
+
79
+ def initialize(query_method:, **options, &definition_block)
80
+ @query_method = query_method
81
+ super(**options.except(:query_method), &definition_block)
69
82
  end
70
83
  end)
71
84
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Groups
5
+ class PendingQuery
6
+ attr_reader :key
7
+ attr_reader :aggregate
8
+ attr_reader :query
9
+
10
+ def initialize(key, aggregate, proc)
11
+ @key = Utils.wrap(key)
12
+ @aggregate = Utils.wrap(aggregate)
13
+ @query = proc
14
+ end
15
+
16
+ def execute
17
+ result = @query.call
18
+ QueryResult.new(@key, @aggregate, result)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Groups
5
+ class QueryBuilder
6
+ def self.parse(lookahead, object, context)
7
+ QueryBuilder.new(lookahead, object, context).group_selections
8
+ end
9
+
10
+ def initialize(lookahead, object, context)
11
+ @lookahead = lookahead
12
+ @context = context
13
+ type = @lookahead.field.type.of_type
14
+ @base_query = proc { type.authorized_new(object, context).scope }
15
+ super()
16
+ end
17
+
18
+ def group_selections(lookahead = @lookahead, current_context = QueryBuilderContext.new([], @base_query))
19
+ selections = lookahead.selections
20
+ group_field_type = lookahead.field.type.of_type.field_class
21
+ group_selections = selections.select { |selection| selection.field.is_a?(group_field_type) }
22
+ queries = group_selections.each_with_object([]) do |selection, object|
23
+ field_proc = proc_from_selection(selection.field, selection.arguments)
24
+ context = current_context.update(selection.name, field_proc)
25
+ object << create_pending_queries(selection, context)
26
+ end
27
+ nested_queries = group_selections
28
+ .filter { |selection| selection.selects?(:group_by) }
29
+ .each_with_object([]) do |selection, object|
30
+ field_proc = proc_from_selection(selection.field, selection.arguments)
31
+ context = current_context.update(selection.name, field_proc)
32
+ object << group_selections(selection.selection(:group_by), context)
33
+ end
34
+ (queries + nested_queries).flatten
35
+ end
36
+
37
+ private
38
+
39
+ def create_pending_queries(current_selection, context)
40
+ aggregate_selections = current_selection
41
+ .selections
42
+ .select { |selection| selection.field.is_a?(GraphQL::Groups::Schema::AggregateField) }
43
+ count_queries = count_queries(aggregate_selections, context)
44
+ aggregate_queries = aggregate_queries(aggregate_selections, context)
45
+ (count_queries + aggregate_queries)
46
+ end
47
+
48
+ def count_queries(aggregate_selections, context)
49
+ # TODO: When getting multiple aggregates for the same base data we could do just a single query instead of many
50
+ aggregate_selections
51
+ .select { |selection| selection.name == :count }
52
+ .map do |selection|
53
+ field = selection.field
54
+ count_proc = proc { |scope| field.owner.send(:new, {}, nil).public_send(field.query_method, scope: scope) }
55
+ combined = combine_procs(context.current_proc, count_proc)
56
+ PendingQuery.new(context.grouping, selection.name, combined)
57
+ end
58
+ end
59
+
60
+ def aggregate_queries(aggregate_selections, context)
61
+ aggregate_selections
62
+ .select { |selection| selection.field.own_attributes.present? }
63
+ .map { |selection| attribute_queries(context, selection) }
64
+ .flatten
65
+ end
66
+
67
+ def attribute_queries(context, selection)
68
+ selection.field
69
+ .own_attributes
70
+ .select { |attribute| selection.selections.map(&:name).include?(attribute) }
71
+ .map do |attribute|
72
+ aggregate_proc = proc_from_attribute(selection.field, attribute, selection.arguments)
73
+ combined = combine_procs(context.current_proc, aggregate_proc)
74
+ PendingQuery.new(context.grouping, [selection.name, attribute], combined)
75
+ end
76
+ end
77
+
78
+ def combine_procs(base_proc, new_proc)
79
+ proc { new_proc.call(base_proc.call) }
80
+ end
81
+
82
+ def proc_from_selection(field, arguments)
83
+ proc { |scope| field.owner.authorized_new(nil, @context).public_send(field.query_method, scope: scope, **arguments) }
84
+ end
85
+
86
+ def proc_from_attribute(field, attribute, arguments)
87
+ proc do |scope|
88
+ field.owner.authorized_new(nil, @context)
89
+ .public_send(field.query_method,
90
+ scope: scope,
91
+ attribute: attribute, **arguments)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class QueryBuilderContext
4
+ attr_reader :grouping
5
+ attr_reader :current_proc
6
+
7
+ def initialize(groupings = [], current_proc = nil)
8
+ @grouping = groupings
9
+ @current_proc = current_proc
10
+ end
11
+
12
+ def update(grouping, new_proc)
13
+ new_grouping = @grouping + [grouping]
14
+ combined_proc = combine_procs(@current_proc, new_proc)
15
+ QueryBuilderContext.new(new_grouping, combined_proc)
16
+ end
17
+
18
+ def combine_procs(base_proc, new_proc)
19
+ proc { new_proc.call(base_proc.call) }
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Groups
5
+ class QueryResult
6
+ attr_reader :key
7
+ attr_reader :aggregate
8
+ attr_reader :result_hash
9
+
10
+ def initialize(key, aggregate, result)
11
+ @key = Utils.wrap(key)
12
+ @aggregate = Utils.wrap(aggregate)
13
+ @result_hash = result
14
+ end
15
+ end
16
+ end
17
+ end
@@ -3,93 +3,47 @@
3
3
  module GraphQL
4
4
  module Groups
5
5
  class ResultTransformer
6
- def run(results)
7
- transform_results(results)
6
+ def run(query_results)
7
+ # Sort by key length so that deeper nested queries come later
8
+ query_results
9
+ .sort_by { |query_result| query_result.key.length }
10
+ .each_with_object({}) do |query_result, object|
11
+ transform_result(query_result, object)
12
+ end
8
13
  end
9
14
 
10
15
  private
11
16
 
12
- def transform_results(results)
13
- # Because group query returns its results in a way that is not usable by GraphQL we need to transform these
14
- # results and merge them into a single dataset.
15
- #
16
- # The result of a group query usually is a hash where they keys are the values of the columns that were grouped
17
- # and the values are the aggregates. What we want is a deep hash where each level contains the statistics for
18
- # that level in regards to the parent level.
19
- #
20
- # It all makes a lot more sense if you look at the GraphQL interface for statistics :)
21
- #
22
- # We accomplish this by transforming each result set to a hash and then merging them into a single one.
23
- results.each_with_object({}) { |(key, value), object| object.deep_merge!(transform_result(key, value)) }
24
- end
25
-
26
- def transform_result(key, result)
27
- is_aggregate_result = result.values[0].values[0].is_a?(Hash)
28
-
29
- transformed = result.each_with_object({}) do |(aggregate_key, aggregate_value), object|
30
- if is_aggregate_result
31
- transform_aggregate_result(aggregate_key, aggregate_value, key, object)
17
+ def transform_result(query_result, object)
18
+ keys = query_result.key
19
+ return object[keys[0]] = [] if query_result.result_hash.empty?
20
+
21
+ query_result.result_hash.each do |grouping_result|
22
+ group_result_keys = Utils.wrap(grouping_result[0])
23
+ group_result_value = grouping_result[1]
24
+ Utils.duplicate(keys, group_result_keys)
25
+ inner_hash = create_nested_result(keys, group_result_keys, object)
26
+ if query_result.aggregate.length == 1
27
+ inner_hash[query_result.aggregate[0]] = group_result_value
32
28
  else
33
- object.deep_merge!(transform_aggregate(key, aggregate_key, aggregate_value))
29
+ aggregate_type = query_result.aggregate[0]
30
+ aggregate_attribute = query_result.aggregate[1]
31
+ inner_hash[aggregate_type] ||= {}
32
+ inner_hash[aggregate_type][aggregate_attribute] ||= group_result_value
34
33
  end
35
34
  end
36
-
37
- transformed.presence || { key => [] }
38
- end
39
-
40
- def transform_aggregate_result(aggregate_key, aggregate_value, key, object)
41
- aggregate_value.each do |attribute, value|
42
- object.deep_merge!(transform_attribute(key, aggregate_key, attribute, value))
43
- end
44
35
  end
45
36
 
46
- def transform_aggregate(key, aggregate, result)
47
- return {} unless result.present?
48
-
49
- hashes = result.map do |(keys, value)|
50
- with_zipped = build_keys(key, keys)
51
- with_zipped.append(aggregate)
52
- with_zipped.reverse.inject(value) { |a, n| { n => a } }
53
- end
54
-
55
- merge(hashes)
56
- end
37
+ def create_nested_result(keys, group_result_keys, object)
38
+ head_key, *rest_keys = keys
39
+ head_group_key, *rest_group_keys = group_result_keys
40
+ object[head_key] ||= {}
41
+ object[head_key][head_group_key] ||= {}
42
+ inner_hash = object[head_key][head_group_key]
43
+ return inner_hash if rest_keys.empty?
57
44
 
58
- def transform_attribute(key, aggregate, attribute, result)
59
- return {} unless result.present?
60
-
61
- hashes = result.map do |(keys, value)|
62
- with_zipped = build_keys(key, keys)
63
- with_zipped.append(aggregate)
64
- with_zipped.append(attribute)
65
- with_zipped.reverse.inject(value) { |a, n| { n => a } }
66
- end
67
- merge(hashes)
68
- end
69
-
70
- def merge(hashes)
71
- hashes.each_with_object({}) do |hash, object|
72
- object.deep_merge!(hash)
73
- end
74
- end
75
-
76
- def build_keys(key, keys)
77
- key = wrap(key)
78
- keys = keys ? wrap(keys) : [nil]
79
- nested = [:nested] * (key.length - 1)
80
-
81
- with_zipped = key.zip(keys).zip(nested).flatten!
82
- with_zipped.first(with_zipped.size - 1)
83
- end
84
-
85
- def wrap(object)
86
- if object.nil?
87
- []
88
- elsif object.respond_to?(:to_ary)
89
- object.to_ary || [object]
90
- else
91
- [object]
92
- end
45
+ inner_hash[:group_by] ||= {}
46
+ create_nested_result(rest_keys, rest_group_keys, inner_hash[:group_by])
93
47
  end
94
48
  end
95
49
  end
@@ -21,7 +21,7 @@ module GraphQL
21
21
  group_result[0]
22
22
  end
23
23
 
24
- def count(scope:, **_)
24
+ def count(scope:, **)
25
25
  scope.size
26
26
  end
27
27
 
@@ -10,8 +10,7 @@ module GraphQL
10
10
 
11
11
  alias group object
12
12
 
13
- # TODO: Make group field inherit from default field, so that users default args/fields are respected
14
- field_class(GroupField)
13
+ field_class(own_field_type)
15
14
  end
16
15
  end
17
16
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Groups
5
+ module Utils
6
+ class << self
7
+ def wrap(object)
8
+ if object.nil?
9
+ []
10
+ elsif object.respond_to?(:to_ary)
11
+ object.to_ary || [object]
12
+ else
13
+ [object]
14
+ end
15
+ end
16
+
17
+ # This is used by the resul transformer when the user executed a query where some groupings are repeated, so depth
18
+ # of the query doesn't match the length of the query result keys. We need to modify the result keys so everything
19
+ # matches again.
20
+ def duplicate(keys, values)
21
+ return if keys.length == values.length
22
+
23
+ duplicates = duplicates(keys)
24
+ return if duplicates.empty?
25
+
26
+ duplicates.each do |_, indices|
27
+ first_occurrence, *rest = indices
28
+ value_to_duplicate = values[first_occurrence]
29
+ rest.each { |index| values.insert(index, value_to_duplicate) }
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def duplicates(array)
36
+ map = {}
37
+ duplicates = {}
38
+ array.each_with_index do |v, i|
39
+ map[v] = (map[v] || 0) + 1
40
+ duplicates[v] ||= []
41
+ duplicates[v] << i
42
+ end
43
+ duplicates.select { |_, v| v.length > 1 }
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Graphql
4
4
  module Groups
5
- VERSION = '0.1.4'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-groups
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hans-Jörg Schnedlitz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-08-26 00:00:00.000000000 Z
11
+ date: 2021-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: groupdate
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 5.2.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 5.2.1
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: gruff
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -222,17 +236,20 @@ files:
222
236
  - bin/setup
223
237
  - graphql-groups.gemspec
224
238
  - lib/graphql/groups.rb
225
- - lib/graphql/groups/executor.rb
226
239
  - lib/graphql/groups/group_type_registry.rb
227
240
  - lib/graphql/groups/has_aggregates.rb
228
241
  - lib/graphql/groups/has_groups.rb
229
- - lib/graphql/groups/lookahead_parser.rb
242
+ - lib/graphql/groups/pending_query.rb
243
+ - lib/graphql/groups/query_builder.rb
244
+ - lib/graphql/groups/query_builder_context.rb
245
+ - lib/graphql/groups/query_result.rb
230
246
  - lib/graphql/groups/result_transformer.rb
231
247
  - lib/graphql/groups/schema/aggregate_field.rb
232
248
  - lib/graphql/groups/schema/aggregate_type.rb
233
249
  - lib/graphql/groups/schema/group_field.rb
234
250
  - lib/graphql/groups/schema/group_result_type.rb
235
251
  - lib/graphql/groups/schema/group_type.rb
252
+ - lib/graphql/groups/utils.rb
236
253
  - lib/graphql/groups/version.rb
237
254
  homepage: https://github.com/hschne/graphql-groups
238
255
  licenses:
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GraphQL
4
- module Groups
5
- class Executor
6
- class << self
7
- def call(base_query, execution_plan)
8
- execution_plan.each_with_object({}) do |(key, value), object|
9
- object.merge!(execute(base_query, key, value))
10
- end
11
- end
12
-
13
- def execute(scope, key, value)
14
- group_query = value[:proc].call(scope: scope)
15
- results = value[:aggregates].each_with_object({}) do |(aggregate_key, aggregate), object|
16
- if aggregate_key == :count
17
- object[:count] = aggregate[:proc].call(scope: group_query)
18
- else
19
- object[aggregate_key] ||= {}
20
- aggregate[:attributes].each do |attribute|
21
- result = aggregate[:proc].call(scope: group_query, attribute: attribute)
22
- object[aggregate_key][attribute] = result
23
- end
24
- end
25
- end
26
-
27
- results = { key => results }
28
- return results unless value[:nested]
29
-
30
- value[:nested].each do |inner_key, inner_value|
31
- new_key = (Array.wrap(key) << inner_key)
32
- inner_result = execute(group_query, inner_key, inner_value)
33
- results[new_key] = inner_result[inner_key]
34
- end
35
- results
36
- end
37
- end
38
- end
39
- end
40
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GraphQL
4
- module Groups
5
- class LookaheadParser
6
- def self.parse(base_selection, context)
7
- # TODO: Raise error if no aggregate selection is made
8
- LookaheadParser.new(context).group_selections(base_selection, {})
9
- end
10
-
11
- def initialize(context)
12
- @context = context
13
- end
14
-
15
- def group_selections(root, hash)
16
- selections = root.selections
17
- group_selections = selections.select { |selection| selection.field.is_a?(GraphQL::Groups::Schema::GroupField) }
18
- group_selections.each do |selection|
19
- own_query = get_field_proc(selection.field, selection.arguments)
20
- hash[selection.name] ||= { proc: own_query }
21
- hash[selection.name][:aggregates] = aggregates(selection)
22
- end
23
- group_selections
24
- .filter { |selection| selection.selects?(:group_by) }
25
- .each { |selection| hash[selection.name][:nested] = group_selections(selection.selection(:group_by), {}) }
26
- hash
27
- end
28
-
29
- def get_field_proc(field, arguments)
30
- proc { |**kwargs| field.owner.authorized_new(nil, @context).public_send(field.query_method, **arguments, **kwargs) }
31
- end
32
-
33
- def aggregates(group_selection)
34
- aggregate_selections = group_selection.selections.select do |selection|
35
- selection.field.is_a?(GraphQL::Groups::Schema::AggregateField)
36
- end
37
- aggregate_selections.each_with_object({}) do |selection, object|
38
- name = selection.name
39
- field = selection.field
40
- if name == :count
41
- proc = proc { |**kwargs| field.owner.send(:new, {}, nil).public_send(field.query_method, **kwargs) }
42
- object[name] = { proc: proc }
43
- elsif selection.field.own_attributes.present?
44
- object[name] = { proc: get_aggregate_proc(field, selection.arguments), attributes: field.own_attributes }
45
- end
46
- end
47
- end
48
-
49
- def get_aggregate_proc(field, arguments)
50
- proc { |**kwargs| field.owner.authorized_new(nil, @context).send(field.query_method, **kwargs, **arguments) }
51
- end
52
- end
53
- end
54
- end