graphql-groups 0.1.4 → 0.2.0

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: 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