graphql_rails 2.2.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|