graphql-decorate 1.0.0 → 1.0.1

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