graphql-decorate 1.0.0 → 1.0.1

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: f51b30fb5841e235310b83ab3d9b697d86b420c1a807bb0500b73dcf231d295b
4
- data.tar.gz: 4c02e6130d6955d6d9fbde06a9fe45352e1bd7f5dba7ec7e1d60c9bb846242c0
3
+ metadata.gz: 87a8fe01c943ec96814efe0e1eff084e8a1a06d07ddfb25bdc957b7b9d7e1359
4
+ data.tar.gz: 834c64c23ce5db962159773e82f10ad7130322cffba0ef340a3dbd244b0ec8e4
5
5
  SHA512:
6
- metadata.gz: 2aaa588eb463291941b8c4678daaaead6479b2526d9799998889186cccece07770cded6ec469108e2b92346317ab804fed0e7fd24acb80679251b2e0cb63dc43
7
- data.tar.gz: 1c2a48487fd6b82739b501968578f039c89e7769db849daf955f152b72ec2e353b43790ea159400580f0e99f8b464c7a8f7cd98e11b7bd7a818ce42df7b2299f
6
+ metadata.gz: 536f14a8113cd9c166667260b743ef2443b1b845a2293d4fc1b006fd9818d819cd90439c6fd186d10c7ba0657eb6bd67840053160a65045edff94a69ace61db4
7
+ data.tar.gz: a083fe5519d3d5c980e01e9aa72f8e9c4f18d05c0130032a617933aba15410a9b32d7ef0b29dae8445dec018b68fc963b1964bb2534f06481594ab7759587498
@@ -1,4 +1,4 @@
1
- name: Ruby
1
+ name: CI
2
2
 
3
3
  on:
4
4
  push:
@@ -7,7 +7,7 @@ on:
7
7
  branches: [ master ]
8
8
 
9
9
  jobs:
10
- test:
10
+ ci:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
@@ -28,12 +28,6 @@ jobs:
28
28
  - name: Run RuboCop
29
29
  run: bundle exec rubocop
30
30
 
31
- - name: Upload coverage results
32
- uses: actions/upload-artifact@master
33
- with:
34
- name: coverage-report
35
- path: coverage
36
-
37
31
  - name: Check documentation completion
38
32
  run: |
39
33
  completion_percentage=$(bundle exec yard | tee /dev/stdout | tail -1 | cut -d'%' -f1 | xargs)
data/README.md CHANGED
@@ -1,3 +1,6 @@
1
+ [![Gem Version](https://badge.fury.io/rb/graphql-decorate.svg)](https://badge.fury.io/rb/graphql-decorate)
2
+ ![CI](https://github.com/TrueCar/graphql-decorate/actions/workflows/ci.yml/badge.svg)
3
+
1
4
  # GraphQL Decorate
2
5
 
3
6
  `graphql-decorate` adds an easy-to-use interface for decorating types in [`graphql-ruby`](https://github.com/rmosolgo/graphql-ruby). It lets
@@ -19,16 +22,19 @@ Or install it yourself as:
19
22
 
20
23
  $ gem install graphql-decorate
21
24
 
22
- Once the gem is installed, you need to add the integrations to your base type and field classes.
25
+ Once the gem is installed, you need to add the plugin to your schema and the integration into
26
+ your base object class.
23
27
  ```ruby
24
- class BaseType < GraphQL::Schema::Object
25
- extend GraphQL::Decorate::ObjectIntegration
28
+ class Schema < GraphQL::Schema
29
+ use GraphQL::Decorate
26
30
  end
27
31
 
28
- class BaseField < GraphQL::Schema::Field
29
- include GraphQL::Decorate::FieldIntegration
32
+ class BaseObject < GraphQL::Schema::Object
33
+ include GraphQL::Decorate::ObjectIntegration
30
34
  end
31
35
  ```
36
+ Note that `use GraphQL::Decorate` must be included in the schema _after_ `query` and `mutation`
37
+ so that the fields to be extended are initialized first.
32
38
 
33
39
  ## Usage
34
40
 
@@ -48,7 +54,7 @@ class RectangleDecorator < BaseDecorator
48
54
  end
49
55
  end
50
56
 
51
- class Rectangle < GraphQL::Schema::Object
57
+ class Rectangle < BaseObject
52
58
  decorate_with RectangleDecorator
53
59
 
54
60
  field :area, Int, null: false
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.add_runtime_dependency 'graphql', '>= 1.3', '< 2'
32
32
 
33
33
  spec.add_development_dependency 'bundler', '>= 2'
34
- spec.add_development_dependency 'rake', '~> 10.0'
34
+ spec.add_development_dependency 'rake', '>= 12.3.3'
35
35
  spec.add_development_dependency 'rspec', '~> 3.0'
36
36
  spec.add_development_dependency 'rubocop', ' >= 1.11.0 '
37
37
  spec.add_development_dependency 'rubocop-rspec', '2.2.0'
@@ -3,19 +3,20 @@
3
3
  require 'graphql'
4
4
  require_relative 'decorate/version'
5
5
  require_relative 'decorate/configuration'
6
+ require_relative 'decorate/extract_type'
6
7
  require_relative 'decorate/object_integration'
7
- require_relative 'decorate/field_integration'
8
8
  require_relative 'decorate/field_extension'
9
9
  require_relative 'decorate/decoration'
10
10
  require_relative 'decorate/type_attributes'
11
11
  require_relative 'decorate/undecorated_field'
12
- require_relative 'decorate/connection_wrapper'
13
12
  require_relative 'decorate/metadata'
14
13
 
15
14
  # Matching the graphql-ruby namespace
16
15
  module GraphQL
17
16
  # Entry point for graphql-decorate. Handles configuration.
18
17
  module Decorate
18
+ extend ExtractType
19
+
19
20
  # @return [Configuration] Returns a new instance of GraphQL::Decorate::Configuration.
20
21
  def self.configuration
21
22
  @configuration ||= Configuration.new
@@ -30,5 +31,19 @@ module GraphQL
30
31
  def self.reset_configuration!
31
32
  @configuration = Configuration.new
32
33
  end
34
+
35
+ # @param schema_defn [GraphQL::Schema] Current schema class
36
+ # @return [nil]
37
+ def self.use(schema_defn)
38
+ schema_defn.to_graphql.types.each do |_name, type|
39
+ next unless type.respond_to?(:fields)
40
+
41
+ type.fields.each do |_name, field|
42
+ field_type = extract_type(field.type_class.type)
43
+ type_attributes = GraphQL::Decorate::TypeAttributes.new(field_type)
44
+ field.type_class.extension(GraphQL::Decorate::FieldExtension) if type_attributes.decoratable?
45
+ end
46
+ end
47
+ end
33
48
  end
34
49
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Allows extraction of a type class from a particular field.
4
+ module ExtractType
5
+ private
6
+
7
+ def extract_type(field)
8
+ if field.respond_to?(:of_type)
9
+ extract_type(field.of_type)
10
+ else
11
+ field
12
+ end
13
+ end
14
+ end
@@ -4,35 +4,35 @@ module GraphQL
4
4
  module Decorate
5
5
  # Extension run after fields are resolved to decorate their value.
6
6
  class FieldExtension < GraphQL::Schema::FieldExtension
7
+ include ExtractType
8
+
7
9
  # Extension to be called after lazy loading.
8
10
  # @param context [GraphQL::Query::Context] The current GraphQL query context.
9
- # @param value [Object, GraphQL::Schema::Object, GraphQL::Pagination::Connection] The object being decorated. Can
11
+ # @param value [Object, Array, GraphQL::Schema::Object] The object being decorated. Can
10
12
  # be a schema object if the field hasn't been resolved yet or a connection.
11
- # @param object [Object] Object the field is being resolved on.
12
- # @return [Object, GraphQL::Decorate::ConnectionWrapper] Decorated object.
13
- def after_resolve(context:, value:, object:, **_rest)
13
+ # @return [Object] Decorated object.
14
+ def after_resolve(context:, value:, **_rest)
14
15
  return if value.nil?
15
16
 
16
- resolve_decorated_value(value, object, context)
17
+ resolve_decorated_value(value, context)
17
18
  end
18
19
 
19
20
  private
20
21
 
21
- def resolve_decorated_value(value, parent_object, context)
22
+ def resolve_decorated_value(value, context)
22
23
  type = extract_type(context.to_h[:current_field].type)
23
- if value.is_a?(GraphQL::Pagination::Connection)
24
- GraphQL::Decorate::ConnectionWrapper.wrap(value, type, context)
25
- elsif collection?(value)
26
- value.map do |item|
27
- decorate(item, type, parent_object.object, parent_object.class, context)
24
+
25
+ if collection?(value)
26
+ value.each_with_index.map do |item, index|
27
+ decorate(item, type, context, index)
28
28
  end
29
29
  else
30
- decorate(value, type, parent_object.object, parent_object.class, context)
30
+ decorate(value, type, context)
31
31
  end
32
32
  end
33
33
 
34
- def decorate(value, type, parent_object, parent_type, context)
35
- undecorated_field = GraphQL::Decorate::UndecoratedField.new(value, type, parent_object, parent_type, context)
34
+ def decorate(value, type, context, index = nil)
35
+ undecorated_field = GraphQL::Decorate::UndecoratedField.new(value, type, context, index)
36
36
  GraphQL::Decorate::Decoration.decorate(undecorated_field)
37
37
  end
38
38
 
@@ -45,14 +45,6 @@ module GraphQL
45
45
  klasses << ::ActiveRecord::Relation if defined?(ActiveRecord::Relation)
46
46
  klasses
47
47
  end
48
-
49
- def extract_type(field)
50
- if field.respond_to?(:of_type)
51
- extract_type(field.of_type)
52
- else
53
- field
54
- end
55
- end
56
48
  end
57
49
  end
58
50
  end
@@ -4,6 +4,12 @@ module GraphQL
4
4
  module Decorate
5
5
  # Extends GraphQL::Schema::Object classes with methods to set the desired decorator class and context.
6
6
  module ObjectIntegration
7
+ # @param base [Class] Base class the module is being included in
8
+ # @return [nil]
9
+ def self.included(base)
10
+ base.extend(self)
11
+ end
12
+
7
13
  # Decorate the type with a decorator class.
8
14
  # @param klass [Class] Class the object should be decorated with.
9
15
  def decorate_with(klass = nil, &block)
@@ -39,7 +39,7 @@ module GraphQL
39
39
 
40
40
  # @return [Boolean] True if type is not yet resolved, false if it is resolved
41
41
  def unresolved_type?
42
- type.respond_to?(:resolve_type)
42
+ type.respond_to?(:kind) && [GraphQL::TypeKinds::INTERFACE, GraphQL::TypeKinds::UNION].include?(type.kind)
43
43
  end
44
44
 
45
45
  # @return [Boolean] True if type is resolved, false if it is not resolved
@@ -47,19 +47,10 @@ module GraphQL
47
47
  !unresolved_type?
48
48
  end
49
49
 
50
- # @return [Boolean] True if type is a connection, false if it is resolved
51
- def connection?
52
- resolved_type? && type.respond_to?(:node_type)
53
- end
54
-
55
50
  private
56
51
 
57
52
  def get_attribute(name)
58
- if connection?
59
- type.node_type.respond_to?(name) && type.node_type.public_send(name)
60
- elsif resolved_type?
61
- type.respond_to?(name) ? type.public_send(name) : nil
62
- end
53
+ type.respond_to?(name) ? type.public_send(name) : nil
63
54
  end
64
55
  end
65
56
  end
@@ -9,16 +9,15 @@ module GraphQL
9
9
 
10
10
  # @param value [Object] Value to be decorated
11
11
  # @param type [GraphQL::Schema::Object] Type class of value to be decorated
12
- # @param parent_value [Object] Value of the parent field
13
- # @param parent_type [GraphQL::Schema::Object] Type class of the parent field
14
12
  # @param graphql_context [GraphQL::Query::Context] Current query graphql_context
15
- def initialize(value, type, parent_value, parent_type, graphql_context)
13
+ def initialize(value, type, graphql_context, index = nil)
16
14
  @value = value
15
+ @type = type
17
16
  @type_attributes = GraphQL::Decorate::TypeAttributes.new(type)
18
- @parent_value = parent_value
19
- @parent_type = parent_type
20
17
  @graphql_context = graphql_context
21
18
  @default_metadata = { graphql: true }
19
+ @path = graphql_context[:current_path].dup
20
+ @path << index if index
22
21
  end
23
22
 
24
23
  # @return [Class] Decorator class for the current field
@@ -37,34 +36,44 @@ module GraphQL
37
36
 
38
37
  private
39
38
 
40
- attr_reader :type_attributes, :graphql_context, :parent_value, :parent_type, :default_metadata
39
+ attr_reader :type, :type_attributes, :graphql_context, :default_metadata, :path
41
40
 
42
41
  def unscoped_metadata
43
42
  unscoped_metadata_proc&.call(value, graphql_context) || {}
44
43
  end
45
44
 
46
45
  def scoped_metadata
47
- merged_scoped_metadata = existing_scoped_metadata.merge(new_scoped_metadata)
48
- graphql_context.scoped_set!(:scoped_decorator_metadata, merged_scoped_metadata)
49
- merged_scoped_metadata
46
+ insert_scoped_metadata(new_scoped_metadata)
50
47
  end
51
48
 
52
49
  def new_scoped_metadata
53
- scoped_metadata = scoped_metadata_proc&.call(value, graphql_context) || {}
54
- parent_scoped_metadata.merge(scoped_metadata)
55
- end
56
-
57
- def parent_scoped_metadata
58
- parent_type_attributes.decorator_metadata&.scoped_proc&.call(parent_value, graphql_context) || {}
59
- end
60
-
61
- def parent_type_attributes
62
- GraphQL::Decorate::TypeAttributes.new(parent_type)
63
- end
50
+ scoped_metadata_proc&.call(value, graphql_context) || {}
51
+ end
52
+
53
+ # rubocop:disable Metrics/AbcSize
54
+ def insert_scoped_metadata(metadata)
55
+ # Save metadata at each level in the path of the current execution.
56
+ # If a field's direct parent does not have metadata then it will
57
+ # use the next highest metadata in the tree that matches its path.
58
+ scoped_metadata = graphql_context[:scoped_decorator_metadata] ||= {}
59
+ prev_value = {}
60
+
61
+ path[0...-1].each do |step|
62
+ # Write the parent's metadata to the child if it doesn't already exist
63
+ scoped_metadata[step] = { value: prev_value, children: {} } unless scoped_metadata[step]
64
+ # Update the next parent's metadata to include anything at the current level
65
+ prev_value = prev_value.merge(scoped_metadata[step][:value])
66
+ # Move to the child fields and repeat
67
+ scoped_metadata = scoped_metadata[step][:children]
68
+ end
64
69
 
65
- def existing_scoped_metadata
66
- graphql_context[:scoped_decorator_metadata] || {}
70
+ # The last step in the path is the current field, merge in new metadata from
71
+ # the field itself and return it.
72
+ merged_metadata = { value: prev_value.merge(metadata), children: {} }
73
+ scoped_metadata[path[-1]] = merged_metadata
74
+ merged_metadata[:value]
67
75
  end
76
+ # rubocop:enable Metrics/AbcSize
68
77
 
69
78
  def unscoped_metadata_proc
70
79
  type_attributes.decorator_metadata&.unscoped_proc || resolve_unscoped_proc
@@ -93,7 +102,11 @@ module GraphQL
93
102
  def resolved_type_attributes
94
103
  @resolved_type_attributes ||= begin
95
104
  if type_attributes.unresolved_type?
96
- GraphQL::Decorate::TypeAttributes.new(type_attributes.type.resolve_type(value, graphql_context))
105
+ if type.respond_to?(:resolve_type)
106
+ GraphQL::Decorate::TypeAttributes.new(type.resolve_type(value, graphql_context))
107
+ else
108
+ graphql_context.schema.resolve_type(type, value, graphql_context)
109
+ end
97
110
  end
98
111
  end
99
112
  end
@@ -3,6 +3,6 @@
3
3
  module GraphQL
4
4
  module Decorate
5
5
  # Current version number
6
- VERSION = '1.0.0'
6
+ VERSION = '1.0.1'
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-decorate
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Brook
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-10 00:00:00.000000000 Z
11
+ date: 2021-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -48,16 +48,16 @@ dependencies:
48
48
  name: rake
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: '10.0'
53
+ version: 12.3.3
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - "~>"
58
+ - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: '10.0'
60
+ version: 12.3.3
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: rspec
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -135,8 +135,8 @@ executables: []
135
135
  extensions: []
136
136
  extra_rdoc_files: []
137
137
  files:
138
+ - ".github/workflows/ci.yml"
138
139
  - ".github/workflows/gem-push-on-release.yml"
139
- - ".github/workflows/ruby.yml"
140
140
  - ".gitignore"
141
141
  - ".rubocop.yml"
142
142
  - Gemfile
@@ -148,10 +148,9 @@ files:
148
148
  - graphql-decorate.gemspec
149
149
  - lib/graphql/decorate.rb
150
150
  - lib/graphql/decorate/configuration.rb
151
- - lib/graphql/decorate/connection_wrapper.rb
152
151
  - lib/graphql/decorate/decoration.rb
152
+ - lib/graphql/decorate/extract_type.rb
153
153
  - lib/graphql/decorate/field_extension.rb
154
- - lib/graphql/decorate/field_integration.rb
155
154
  - lib/graphql/decorate/metadata.rb
156
155
  - lib/graphql/decorate/object_integration.rb
157
156
  - lib/graphql/decorate/type_attributes.rb
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GraphQL
4
- module Decorate
5
- # Wraps a GraphQL::Pagination::ConnectionWrapper object to decorate values after pagination is applied.
6
- class ConnectionWrapper
7
- # @param connection [GraphQL::Pagination::Connection] ConnectionWrapper being decorated
8
- # @param node_type [GraphQL::Schema::Object] Type class of the connection's node
9
- # @param context [GraphQL::Query::Context] Current query context
10
- def self.wrap(connection, node_type, context)
11
- @connection_class = connection.class
12
- new(connection, node_type, context)
13
- end
14
-
15
- # @return [GraphQL::Pagination::Connection] ConnectionWrapper being decorated
16
- attr_reader :connection
17
-
18
- # @param connection [GraphQL::Pagination::Connection] ConnectionWrapper being decorated
19
- # # @param node_type [GraphQL::Schema::Object] Type class of the connection's node
20
- # @param context [GraphQL::Query::Context] Current query context
21
- def initialize(connection, node_type, context)
22
- @connection = connection
23
- @node_type = node_type
24
- @context = context
25
- end
26
-
27
- # @return [Array] Decorated nodes after pagination is applied
28
- def nodes
29
- nodes = @connection.nodes
30
- nodes.map do |node|
31
- unresolved_field = GraphQL::Decorate::UndecoratedField.new(node, node_type, connection.parent,
32
- connection.field.owner, context)
33
- GraphQL::Decorate::Decoration.decorate(unresolved_field)
34
- end
35
- end
36
-
37
- # @see nodes
38
- # @return [Array] Decorated nodes after pagination is applied
39
- def edge_nodes
40
- nodes
41
- end
42
-
43
- class << self
44
- private
45
-
46
- def method_missing(symbol, *args, &block)
47
- @connection_class.send(symbol, *args, &block) || super
48
- end
49
-
50
- def respond_to_missing?(method, include_private = false)
51
- @connection_class.respond_to?(method, include_private)
52
- end
53
- end
54
-
55
- private
56
-
57
- attr_reader :node_type, :context
58
-
59
- def method_missing(symbol, *args, &block)
60
- @connection.send(symbol, *args, &block) || super
61
- end
62
-
63
- def respond_to_missing?(method, include_private = false)
64
- @connection.respond_to?(method, include_private)
65
- end
66
- end
67
- end
68
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GraphQL
4
- module Decorate
5
- # Extends default field behavior and adds extension to the field if it should be decorated.
6
- module FieldIntegration
7
- # Overridden field initializer
8
- # @param type [GraphQL::Schema::Object] The type to add the extension to.
9
- # @return [Void]
10
- def initialize(type:, **rest, &block)
11
- super
12
- field_type = [type].flatten(1).first
13
- type_attributes = GraphQL::Decorate::TypeAttributes.new(field_type)
14
- extension(GraphQL::Decorate::FieldExtension) if type_attributes.decoratable?
15
- end
16
- end
17
- end
18
- end