graphql_rails 2.2.0 → 2.4.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 +4 -4
- data/.github/workflows/ruby.yml +3 -2
- data/.ruby-version +1 -1
- data/CHANGELOG.md +16 -0
- data/Gemfile.lock +17 -17
- data/docs/README.md +23 -45
- data/docs/_sidebar.md +0 -1
- data/docs/components/controller.md +15 -1
- data/docs/components/decorator.md +1 -1
- data/docs/components/model.md +100 -5
- data/docs/components/routes.md +61 -15
- data/graphql_rails.gemspec +1 -1
- data/lib/generators/graphql_rails/templates/graphql_controller.erb +1 -1
- data/lib/graphql_rails/attributes/attribute.rb +16 -14
- data/lib/graphql_rails/attributes/attribute_configurable.rb +24 -0
- data/lib/graphql_rails/attributes/attribute_name_parser.rb +4 -4
- data/lib/graphql_rails/attributes/input_attribute.rb +19 -2
- data/lib/graphql_rails/attributes/type_parseable.rb +4 -5
- data/lib/graphql_rails/controller/action_configuration.rb +1 -1
- data/lib/graphql_rails/controller/build_controller_action_resolver.rb +2 -0
- data/lib/graphql_rails/controller/configuration.rb +1 -1
- data/lib/graphql_rails/controller/request/format_errors.rb +1 -1
- data/lib/graphql_rails/controller/request.rb +3 -2
- data/lib/graphql_rails/controller.rb +1 -1
- data/lib/graphql_rails/decorator/relation_decorator.rb +24 -20
- data/lib/graphql_rails/decorator.rb +12 -4
- data/lib/graphql_rails/errors/custom_execution_error.rb +1 -1
- data/lib/graphql_rails/errors/execution_error.rb +1 -1
- data/lib/graphql_rails/errors/system_error.rb +11 -1
- data/lib/graphql_rails/errors/validation_error.rb +14 -1
- data/lib/graphql_rails/model/find_or_build_graphql_input_type.rb +28 -0
- data/lib/graphql_rails/model/find_or_build_graphql_type.rb +14 -5
- data/lib/graphql_rails/model/find_or_build_graphql_type_class.rb +4 -3
- data/lib/graphql_rails/model/input.rb +3 -3
- data/lib/graphql_rails/router/build_schema_action_type.rb +112 -0
- data/lib/graphql_rails/router/event_route.rb +52 -0
- data/lib/graphql_rails/router/mutation_route.rb +1 -1
- data/lib/graphql_rails/router/query_route.rb +1 -1
- data/lib/graphql_rails/router/resource_routes_builder.rb +0 -8
- data/lib/graphql_rails/router/route.rb +4 -3
- data/lib/graphql_rails/router/schema_builder.rb +18 -26
- data/lib/graphql_rails/router.rb +32 -16
- data/lib/graphql_rails/rspec_controller_helpers.rb +3 -1
- data/lib/graphql_rails/types/hidable_by_group.rb +23 -3
- data/lib/graphql_rails/types/input_object_type.rb +16 -0
- data/lib/graphql_rails/version.rb +1 -1
- metadata +11 -16
- data/docs/getting_started/quick_start.md +0 -62
- data/lib/graphql_rails/model/build_graphql_input_type.rb +0 -43
- data/lib/graphql_rails/router/subscription_route.rb +0 -22
@@ -17,6 +17,7 @@ module GraphqlRails
|
|
17
17
|
|
18
18
|
chainable_option :description
|
19
19
|
chainable_option :options, default: {}
|
20
|
+
chainable_option :extras, default: []
|
20
21
|
chainable_option :type
|
21
22
|
end
|
22
23
|
|
@@ -32,6 +33,14 @@ module GraphqlRails
|
|
32
33
|
groups(*args)
|
33
34
|
end
|
34
35
|
|
36
|
+
def hidden_in_groups(new_groups = ChainableOptions::NOT_SET)
|
37
|
+
@hidden_in_groups ||= []
|
38
|
+
return @hidden_in_groups if new_groups == ChainableOptions::NOT_SET
|
39
|
+
|
40
|
+
@hidden_in_groups = Array(new_groups).map(&:to_s)
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
35
44
|
def required(new_value = true) # rubocop:disable Style/OptionalBooleanParameter
|
36
45
|
@required = new_value
|
37
46
|
self
|
@@ -40,6 +49,21 @@ module GraphqlRails
|
|
40
49
|
def optional(new_value = true) # rubocop:disable Style/OptionalBooleanParameter
|
41
50
|
required(!new_value)
|
42
51
|
end
|
52
|
+
|
53
|
+
def deprecated(reason = 'Deprecated')
|
54
|
+
@deprecation_reason = \
|
55
|
+
if [false, nil].include?(reason)
|
56
|
+
nil
|
57
|
+
else
|
58
|
+
reason.is_a?(String) ? reason : 'Deprecated'
|
59
|
+
end
|
60
|
+
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def deprecation_reason
|
65
|
+
@deprecation_reason
|
66
|
+
end
|
43
67
|
end
|
44
68
|
end
|
45
69
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module GraphqlRails
|
4
4
|
module Attributes
|
5
5
|
# Parses attribute name and can generates graphql scalar type,
|
6
|
-
#
|
6
|
+
# graphql name and etc. based on that
|
7
7
|
class AttributeNameParser
|
8
8
|
def initialize(original_name, options: {})
|
9
9
|
@original_name = original_name.to_s
|
@@ -13,9 +13,9 @@ module GraphqlRails
|
|
13
13
|
def field_name
|
14
14
|
@field_name ||= \
|
15
15
|
if original_format?
|
16
|
-
|
16
|
+
preprocessed_name
|
17
17
|
else
|
18
|
-
|
18
|
+
preprocessed_name.camelize(:lower)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -47,7 +47,7 @@ module GraphqlRails
|
|
47
47
|
options[:input_format] == :original || options[:attribute_name_format] == :original
|
48
48
|
end
|
49
49
|
|
50
|
-
def
|
50
|
+
def preprocessed_name
|
51
51
|
if name.end_with?('?')
|
52
52
|
"is_#{name.remove(/\?\Z/)}"
|
53
53
|
else
|
@@ -12,6 +12,7 @@ module GraphqlRails
|
|
12
12
|
|
13
13
|
chainable_option :subtype
|
14
14
|
chainable_option :enum
|
15
|
+
chainable_option :default_value
|
15
16
|
|
16
17
|
def initialize(name, config:)
|
17
18
|
@config = config
|
@@ -25,7 +26,15 @@ module GraphqlRails
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def input_argument_options
|
28
|
-
{
|
29
|
+
{
|
30
|
+
required: required?,
|
31
|
+
description: description,
|
32
|
+
camelize: false,
|
33
|
+
groups: groups,
|
34
|
+
hidden_in_groups: hidden_in_groups,
|
35
|
+
**default_value_option,
|
36
|
+
**deprecation_reason_params
|
37
|
+
}
|
29
38
|
end
|
30
39
|
|
31
40
|
def paginated?
|
@@ -36,12 +45,20 @@ module GraphqlRails
|
|
36
45
|
|
37
46
|
attr_reader :initial_name, :config
|
38
47
|
|
48
|
+
def default_value_option
|
49
|
+
{ default_value: default_value }.compact
|
50
|
+
end
|
51
|
+
|
39
52
|
def attribute_name_parser
|
40
53
|
@attribute_name_parser ||= AttributeNameParser.new(
|
41
54
|
initial_name, options: attribute_naming_options
|
42
55
|
)
|
43
56
|
end
|
44
57
|
|
58
|
+
def deprecation_reason_params
|
59
|
+
{ deprecation_reason: deprecation_reason }.compact
|
60
|
+
end
|
61
|
+
|
45
62
|
def attribute_naming_options
|
46
63
|
options.slice(:input_format)
|
47
64
|
end
|
@@ -63,7 +80,7 @@ module GraphqlRails
|
|
63
80
|
end
|
64
81
|
|
65
82
|
def raw_input_type
|
66
|
-
return type if type.is_a?(GraphQL::
|
83
|
+
return type if type.is_a?(GraphQL::Schema::InputObject)
|
67
84
|
return type.graphql_input_type if type.is_a?(Model::Input)
|
68
85
|
end
|
69
86
|
end
|
@@ -42,14 +42,13 @@ module GraphqlRails
|
|
42
42
|
WRAPPER_TYPES = [
|
43
43
|
GraphQL::Schema::List,
|
44
44
|
GraphQL::Schema::NonNull,
|
45
|
-
GraphQL::NonNullType,
|
46
|
-
GraphQL::ListType
|
45
|
+
GraphQL::Language::Nodes::NonNullType,
|
46
|
+
GraphQL::Language::Nodes::ListType
|
47
47
|
].freeze
|
48
48
|
|
49
49
|
GRAPHQL_BASE_TYPES = [
|
50
|
-
GraphQL::
|
51
|
-
GraphQL::
|
52
|
-
GraphQL::InputObjectType
|
50
|
+
GraphQL::Schema::Object,
|
51
|
+
GraphQL::Schema::InputObject
|
53
52
|
].freeze
|
54
53
|
|
55
54
|
RAW_GRAPHQL_TYPES = (WRAPPER_TYPES + GRAPHQL_BASE_TYPES).freeze
|
@@ -7,7 +7,7 @@ require 'graphql_rails/errors/error'
|
|
7
7
|
|
8
8
|
module GraphqlRails
|
9
9
|
class Controller
|
10
|
-
# stores all graphql_rails
|
10
|
+
# stores all graphql_rails controller specific config
|
11
11
|
class ActionConfiguration
|
12
12
|
class MissingConfigurationError < GraphqlRails::Error; end
|
13
13
|
class DeprecatedDefaultModelError < GraphqlRails::Error; end
|
@@ -19,6 +19,8 @@ module GraphqlRails
|
|
19
19
|
action = build_action
|
20
20
|
|
21
21
|
Class.new(ControllerActionResolver) do
|
22
|
+
graphql_name("ControllerActionResolver#{SecureRandom.hex}")
|
23
|
+
|
22
24
|
type(*action.type_args, **action.type_options)
|
23
25
|
description(action.description)
|
24
26
|
controller(action.controller)
|
@@ -7,7 +7,7 @@ require 'graphql_rails/errors/error'
|
|
7
7
|
|
8
8
|
module GraphqlRails
|
9
9
|
class Controller
|
10
|
-
# stores all graphql_rails
|
10
|
+
# stores all graphql_rails controller specific config
|
11
11
|
class Configuration
|
12
12
|
class InvalidActionConfiguration < GraphqlRails::Error; end
|
13
13
|
|
@@ -8,7 +8,7 @@ require 'graphql_rails/errors/custom_execution_error'
|
|
8
8
|
module GraphqlRails
|
9
9
|
class Controller
|
10
10
|
class Request
|
11
|
-
# Converts user provided free-form errors in to
|
11
|
+
# Converts user provided free-form errors in to meaningful graphql error classes
|
12
12
|
class FormatErrors
|
13
13
|
include Service
|
14
14
|
|
@@ -7,11 +7,12 @@ module GraphqlRails
|
|
7
7
|
require 'graphql_rails/controller/request/format_errors'
|
8
8
|
|
9
9
|
attr_accessor :object_to_return
|
10
|
-
attr_reader :errors, :context
|
10
|
+
attr_reader :errors, :context, :lookahead
|
11
11
|
|
12
12
|
def initialize(graphql_object, inputs, context)
|
13
13
|
@graphql_object = graphql_object
|
14
|
-
@inputs = inputs
|
14
|
+
@inputs = inputs.except(:lookahead)
|
15
|
+
@lookahead = inputs[:lookahead]
|
15
16
|
@context = context
|
16
17
|
end
|
17
18
|
|
@@ -2,50 +2,51 @@
|
|
2
2
|
|
3
3
|
module GraphqlRails
|
4
4
|
module Decorator
|
5
|
-
#
|
5
|
+
# wraps active record relation and returns decorated object instead
|
6
6
|
class RelationDecorator
|
7
7
|
delegate :map, :each, to: :to_a
|
8
|
-
delegate :limit_value, :offset_value, :count, :size, :empty?, to: :relation
|
8
|
+
delegate :limit_value, :offset_value, :count, :size, :empty?, :loaded?, to: :relation
|
9
9
|
|
10
10
|
def self.decorates?(object)
|
11
11
|
(defined?(ActiveRecord) && object.is_a?(ActiveRecord::Relation)) ||
|
12
12
|
defined?(Mongoid) && object.is_a?(Mongoid::Criteria)
|
13
13
|
end
|
14
14
|
|
15
|
-
def initialize(decorator:, relation:, decorator_args: [])
|
15
|
+
def initialize(decorator:, relation:, decorator_args: [], decorator_kwargs: {})
|
16
16
|
@relation = relation
|
17
17
|
@decorator = decorator
|
18
18
|
@decorator_args = decorator_args
|
19
|
+
@decorator_kwargs = decorator_kwargs
|
19
20
|
end
|
20
21
|
|
21
22
|
%i[where limit order group offset from select having all unscope].each do |method_name|
|
22
|
-
define_method method_name do |*args, &block|
|
23
|
-
chainable_method(method_name, *args, &block)
|
23
|
+
define_method method_name do |*args, **kwargs, &block|
|
24
|
+
chainable_method(method_name, *args, **kwargs, &block)
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
27
28
|
%i[first second last find find_by].each do |method_name|
|
28
|
-
define_method method_name do |*args, &block|
|
29
|
-
decoratable_object_method(method_name, *args, &block)
|
29
|
+
define_method method_name do |*args, **kwargs, &block|
|
30
|
+
decoratable_object_method(method_name, *args, **kwargs, &block)
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
33
34
|
%i[find_each].each do |method_name|
|
34
|
-
define_method method_name do |*args, &block|
|
35
|
-
decoratable_block_method(method_name, *args, &block)
|
35
|
+
define_method method_name do |*args, **kwargs, &block|
|
36
|
+
decoratable_block_method(method_name, *args, **kwargs, &block)
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
39
40
|
def to_a
|
40
|
-
@to_a ||= relation.to_a.map { |it| decorator.new(it, *decorator_args) }
|
41
|
+
@to_a ||= relation.to_a.map { |it| decorator.new(it, *decorator_args, **decorator_kwargs) }
|
41
42
|
end
|
42
43
|
|
43
44
|
private
|
44
45
|
|
45
|
-
attr_reader :relation, :decorator, :decorator_args
|
46
|
+
attr_reader :relation, :decorator, :decorator_args, :decorator_kwargs
|
46
47
|
|
47
|
-
def decoratable_object_method(method_name, *args, &block)
|
48
|
-
object = relation.public_send(method_name, *args, &block)
|
48
|
+
def decoratable_object_method(method_name, *args, **kwargs, &block)
|
49
|
+
object = relation.public_send(method_name, *args, **kwargs, &block)
|
49
50
|
decorate(object)
|
50
51
|
end
|
51
52
|
|
@@ -53,22 +54,25 @@ module GraphqlRails
|
|
53
54
|
return object_or_list if object_or_list.blank?
|
54
55
|
|
55
56
|
if object_or_list.is_a?(Array)
|
56
|
-
object_or_list.map { |it| decorator.new(it, *decorator_args) }
|
57
|
+
object_or_list.map { |it| decorator.new(it, *decorator_args, **decorator_kwargs) }
|
57
58
|
else
|
58
|
-
decorator.new(object_or_list, *decorator_args)
|
59
|
+
decorator.new(object_or_list, *decorator_args, **decorator_kwargs)
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
62
|
-
def decoratable_block_method(method_name, *args)
|
63
|
-
relation.public_send(method_name, *args) do |object, *other_args|
|
63
|
+
def decoratable_block_method(method_name, *args, **kwargs)
|
64
|
+
relation.public_send(method_name, *args, **kwargs) do |object, *other_args|
|
64
65
|
decorated_object = decorate(object)
|
65
66
|
yield(decorated_object, *other_args)
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
69
|
-
def chainable_method(method_name, *args, &block)
|
70
|
-
new_relation = relation.public_send(method_name, *args, &block)
|
71
|
-
self.class.new(
|
70
|
+
def chainable_method(method_name, *args, **kwargs, &block)
|
71
|
+
new_relation = relation.public_send(method_name, *args, **kwargs, &block)
|
72
|
+
self.class.new(
|
73
|
+
decorator: decorator, relation: new_relation,
|
74
|
+
decorator_args: decorator_args, decorator_kwargs: decorator_kwargs
|
75
|
+
)
|
72
76
|
end
|
73
77
|
end
|
74
78
|
end
|
@@ -25,17 +25,25 @@ module GraphqlRails
|
|
25
25
|
extend ActiveSupport::Concern
|
26
26
|
|
27
27
|
class_methods do
|
28
|
-
def decorate(object, *args)
|
28
|
+
def decorate(object, *args, **kwargs)
|
29
29
|
if Decorator::RelationDecorator.decorates?(object)
|
30
|
-
|
30
|
+
decorate_with_relation_decorator(object, args, kwargs)
|
31
31
|
elsif object.nil?
|
32
32
|
nil
|
33
33
|
elsif object.is_a?(Array)
|
34
|
-
object.map { |item| new(item, *args) }
|
34
|
+
object.map { |item| new(item, *args, **kwargs) }
|
35
35
|
else
|
36
|
-
new(object, *args)
|
36
|
+
new(object, *args, **kwargs)
|
37
37
|
end
|
38
38
|
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def decorate_with_relation_decorator(object, args, kwargs)
|
43
|
+
Decorator::RelationDecorator.new(
|
44
|
+
relation: object, decorator: self, decorator_args: args, decorator_kwargs: kwargs
|
45
|
+
)
|
46
|
+
end
|
39
47
|
end
|
40
48
|
end
|
41
49
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module GraphqlRails
|
4
|
-
# base class which is returned in case something bad happens. Contains all error rendering
|
4
|
+
# base class which is returned in case something bad happens. Contains all error rendering structure
|
5
5
|
class CustomExecutionError < ExecutionError
|
6
6
|
attr_reader :extra_graphql_data
|
7
7
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module GraphqlRails
|
4
4
|
require 'graphql'
|
5
5
|
|
6
|
-
# base class which is returned in case something bad happens. Contains all error rendering
|
6
|
+
# base class which is returned in case something bad happens. Contains all error rendering structure
|
7
7
|
class ExecutionError < GraphQL::ExecutionError
|
8
8
|
def to_h
|
9
9
|
super.merge(extra_graphql_data)
|
@@ -1,8 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module GraphqlRails
|
4
|
-
#
|
4
|
+
# Base class which is returned in case something bad happens. Contains all error rendering structure
|
5
5
|
class SystemError < ExecutionError
|
6
|
+
delegate :backtrace, to: :original_error
|
7
|
+
|
8
|
+
attr_reader :original_error
|
9
|
+
|
10
|
+
def initialize(original_error)
|
11
|
+
super(original_error.message)
|
12
|
+
|
13
|
+
@original_error = original_error
|
14
|
+
end
|
15
|
+
|
6
16
|
def to_h
|
7
17
|
super.except('locations')
|
8
18
|
end
|
@@ -3,10 +3,12 @@
|
|
3
3
|
module GraphqlRails
|
4
4
|
# GraphQL error that is raised when invalid data is given
|
5
5
|
class ValidationError < ExecutionError
|
6
|
+
BASE_FIELD_NAME = 'base'
|
7
|
+
|
6
8
|
attr_reader :short_message, :field
|
7
9
|
|
8
10
|
def initialize(short_message, field)
|
9
|
-
super([field
|
11
|
+
super([humanized_field(field), short_message].compact.join(' '))
|
10
12
|
@short_message = short_message
|
11
13
|
@field = field
|
12
14
|
end
|
@@ -18,5 +20,16 @@ module GraphqlRails
|
|
18
20
|
def to_h
|
19
21
|
super.merge('field' => field, 'short_message' => short_message)
|
20
22
|
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def humanized_field(field)
|
27
|
+
return if field.blank?
|
28
|
+
|
29
|
+
stringified_field = field.to_s
|
30
|
+
return if stringified_field == BASE_FIELD_NAME
|
31
|
+
|
32
|
+
stringified_field.humanize
|
33
|
+
end
|
21
34
|
end
|
22
35
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'graphql_rails/types/input_object_type'
|
4
|
+
require 'graphql_rails/concerns/service'
|
5
|
+
require 'graphql_rails/model/find_or_build_graphql_type'
|
6
|
+
|
7
|
+
module GraphqlRails
|
8
|
+
module Model
|
9
|
+
# stores information about model specific config, like attributes and types
|
10
|
+
class FindOrBuildGraphqlInputType < FindOrBuildGraphqlType
|
11
|
+
include ::GraphqlRails::Service
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def parent_class
|
16
|
+
GraphqlRails::Types::InputObjectType
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_attributes_batch(attributes)
|
20
|
+
klass.class_eval do
|
21
|
+
attributes.each do |attribute|
|
22
|
+
argument(*attribute.input_argument_args, **attribute.input_argument_options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -19,7 +19,7 @@ module GraphqlRails
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def call
|
22
|
-
klass.tap {
|
22
|
+
klass.tap { add_attributes if new_class? || force_define_attributes }
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
@@ -28,20 +28,29 @@ module GraphqlRails
|
|
28
28
|
|
29
29
|
delegate :klass, :new_class?, to: :type_class_finder
|
30
30
|
|
31
|
+
def parent_class
|
32
|
+
GraphqlRails::Types::ObjectType
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_attributes_batch(attributes)
|
36
|
+
AddFieldsToGraphqlType.call(klass: klass, attributes: attributes)
|
37
|
+
end
|
38
|
+
|
31
39
|
def type_class_finder
|
32
40
|
@type_class_finder ||= FindOrBuildGraphqlTypeClass.new(
|
33
41
|
name: name,
|
34
42
|
type_name: type_name,
|
35
|
-
description: description
|
43
|
+
description: description,
|
44
|
+
parent_class: parent_class
|
36
45
|
)
|
37
46
|
end
|
38
47
|
|
39
|
-
def
|
48
|
+
def add_attributes
|
40
49
|
scalar_attributes, dynamic_attributes = attributes.values.partition(&:scalar_type?)
|
41
50
|
|
42
|
-
|
51
|
+
add_attributes_batch(scalar_attributes)
|
43
52
|
dynamic_attributes.each { |attribute| find_or_build_dynamic_type(attribute) }
|
44
|
-
|
53
|
+
add_attributes_batch(dynamic_attributes)
|
45
54
|
end
|
46
55
|
|
47
56
|
def find_or_build_dynamic_type(attribute)
|
@@ -9,11 +9,12 @@ module GraphqlRails
|
|
9
9
|
|
10
10
|
include ::GraphqlRails::Service
|
11
11
|
|
12
|
-
def initialize(name:, type_name:, description: nil)
|
12
|
+
def initialize(name:, type_name:, parent_class:, description: nil)
|
13
13
|
@name = name
|
14
14
|
@type_name = type_name
|
15
15
|
@description = description
|
16
16
|
@new_class = false
|
17
|
+
@parent_class = parent_class
|
17
18
|
end
|
18
19
|
|
19
20
|
def klass
|
@@ -27,13 +28,13 @@ module GraphqlRails
|
|
27
28
|
private
|
28
29
|
|
29
30
|
attr_accessor :new_class
|
30
|
-
attr_reader :name, :type_name, :description
|
31
|
+
attr_reader :name, :type_name, :description, :parent_class
|
31
32
|
|
32
33
|
def build_graphql_type_klass
|
33
34
|
graphql_type_name = name
|
34
35
|
graphql_type_description = description
|
35
36
|
|
36
|
-
graphql_type_klass = Class.new(
|
37
|
+
graphql_type_klass = Class.new(parent_class) do
|
37
38
|
graphql_name(graphql_type_name)
|
38
39
|
description(graphql_type_description)
|
39
40
|
end
|
@@ -6,7 +6,7 @@ module GraphqlRails
|
|
6
6
|
class Input
|
7
7
|
require 'graphql_rails/concerns/chainable_options'
|
8
8
|
require 'graphql_rails/model/configurable'
|
9
|
-
require 'graphql_rails/model/
|
9
|
+
require 'graphql_rails/model/find_or_build_graphql_input_type'
|
10
10
|
|
11
11
|
include Configurable
|
12
12
|
|
@@ -18,8 +18,8 @@ module GraphqlRails
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def graphql_input_type
|
21
|
-
@graphql_input_type ||=
|
22
|
-
name: name, description: description, attributes: attributes
|
21
|
+
@graphql_input_type ||= FindOrBuildGraphqlInputType.call(
|
22
|
+
name: name, description: description, attributes: attributes, type_name: type_name
|
23
23
|
)
|
24
24
|
end
|
25
25
|
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphqlRails
|
4
|
+
class Router
|
5
|
+
# Builds GraphQL type used in graphql schema
|
6
|
+
class BuildSchemaActionType
|
7
|
+
ROUTES_KEY = :__routes__
|
8
|
+
|
9
|
+
# @private
|
10
|
+
class SchemaActionType < GraphQL::Schema::Object
|
11
|
+
def self.inspect
|
12
|
+
"#{GraphQL::Schema::Object}(#{graphql_name})"
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def fields_for_nested_routes(type_name_prefix:, scoped_routes:)
|
17
|
+
routes_by_scope = scoped_routes.dup
|
18
|
+
unscoped_routes = routes_by_scope.delete(ROUTES_KEY) || []
|
19
|
+
|
20
|
+
scoped_only_fields(type_name_prefix, routes_by_scope)
|
21
|
+
unscoped_routes.each { route_field(_1) }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def route_field(route)
|
27
|
+
field(*route.name, **route.field_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def scoped_only_fields(type_name_prefix, routes_by_scope)
|
31
|
+
routes_by_scope.each_pair do |scope_name, inner_scope_routes|
|
32
|
+
scope_field(scope_name, "#{type_name_prefix}#{scope_name.to_s.camelize}", inner_scope_routes)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def scope_field(scope_name, scope_type_name, scoped_routes)
|
37
|
+
scope_type = build_scope_type_class(
|
38
|
+
type_name: scope_type_name,
|
39
|
+
scoped_routes: scoped_routes
|
40
|
+
)
|
41
|
+
|
42
|
+
field(scope_name.to_s.camelize(:lower), scope_type, null: false)
|
43
|
+
define_method(scope_type_name.underscore) { self }
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_scope_type_class(type_name:, scoped_routes:)
|
47
|
+
Class.new(SchemaActionType) do
|
48
|
+
graphql_name("#{type_name}Scope")
|
49
|
+
|
50
|
+
fields_for_nested_routes(
|
51
|
+
type_name_prefix: type_name,
|
52
|
+
scoped_routes: scoped_routes
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.call(**kwargs)
|
60
|
+
new(**kwargs).call
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize(type_name:, routes:)
|
64
|
+
@type_name = type_name
|
65
|
+
@routes = routes
|
66
|
+
end
|
67
|
+
|
68
|
+
def call
|
69
|
+
type_name = self.type_name
|
70
|
+
scoped_routes = self.scoped_routes
|
71
|
+
|
72
|
+
Class.new(SchemaActionType) do
|
73
|
+
graphql_name(type_name)
|
74
|
+
|
75
|
+
fields_for_nested_routes(
|
76
|
+
type_name_prefix: type_name,
|
77
|
+
scoped_routes: scoped_routes
|
78
|
+
)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
attr_reader :type_name, :routes
|
85
|
+
|
86
|
+
def scoped_routes
|
87
|
+
routes.each_with_object({}) do |route, result|
|
88
|
+
scope_names = route.scope_names.map { _1.to_s.camelize(:lower) }
|
89
|
+
path_to_routes = scope_names + [ROUTES_KEY]
|
90
|
+
deep_append(result, path_to_routes, route)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# adds array element to nested hash
|
95
|
+
# usage:
|
96
|
+
# deep_hash = { a: { b: [1] } }
|
97
|
+
# deep_append(deep_hash, [:a, :b], 2)
|
98
|
+
# deep_hash #=> { a: { b: [1, 2] } }
|
99
|
+
def deep_append(hash, keys, value)
|
100
|
+
deepest_hash = hash
|
101
|
+
*other_keys, last_key = keys
|
102
|
+
|
103
|
+
other_keys.each do |key|
|
104
|
+
deepest_hash[key] ||= {}
|
105
|
+
deepest_hash = deepest_hash[key]
|
106
|
+
end
|
107
|
+
deepest_hash[last_key] ||= []
|
108
|
+
deepest_hash[last_key] += [value]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|