graphql_rails 2.1.0 → 2.3.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 +1 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +117 -116
- 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 +62 -0
- data/docs/components/routes.md +45 -8
- data/lib/graphql_rails/attributes/attribute.rb +8 -14
- data/lib/graphql_rails/attributes/attribute_configurable.rb +15 -0
- data/lib/graphql_rails/attributes/input_attribute.rb +17 -1
- data/lib/graphql_rails/attributes/type_parseable.rb +1 -7
- data/lib/graphql_rails/controller/build_controller_action_resolver.rb +2 -0
- data/lib/graphql_rails/controller/log_controller_action.rb +7 -2
- data/lib/graphql_rails/controller.rb +1 -1
- data/lib/graphql_rails/decorator/relation_decorator.rb +22 -18
- data/lib/graphql_rails/decorator.rb +12 -4
- data/lib/graphql_rails/errors/system_error.rb +11 -1
- data/lib/graphql_rails/errors/validation_error.rb +14 -1
- data/lib/graphql_rails/input_configurable.rb +1 -1
- data/lib/graphql_rails/model/find_or_build_graphql_type.rb +1 -5
- data/lib/graphql_rails/router/build_schema_action_type.rb +112 -0
- data/lib/graphql_rails/router/mutation_route.rb +4 -0
- data/lib/graphql_rails/router/query_route.rb +4 -0
- data/lib/graphql_rails/router/resource_routes_builder.rb +8 -0
- data/lib/graphql_rails/router/route.rb +3 -2
- data/lib/graphql_rails/router/schema_builder.rb +14 -18
- data/lib/graphql_rails/router/subscription_route.rb +22 -0
- data/lib/graphql_rails/router.rb +32 -10
- data/lib/graphql_rails/version.rb +1 -1
- metadata +5 -4
- data/docs/getting_started/quick_start.md +0 -62
data/docs/components/routes.md
CHANGED
@@ -73,14 +73,15 @@ end
|
|
73
73
|
|
74
74
|
This will generate `userDetails` field on GraphQL side.
|
75
75
|
|
76
|
-
## _query_ and _mutation_
|
76
|
+
## _query_ and _mutation_ & _subscription_
|
77
77
|
|
78
|
-
in case you want to have non-CRUD controller with custom actions you can define your own `query`/`mutation` actions like this:
|
78
|
+
in case you want to have non-CRUD controller with custom actions you can define your own `query`/`mutation`/`subscription` actions like this:
|
79
79
|
|
80
80
|
```ruby
|
81
81
|
MyGraphqlSchema = GraphqlRails::Router.draw do
|
82
82
|
mutation :logIn, to: 'sessions#login'
|
83
|
-
query :me, to 'users#current_user'
|
83
|
+
query :me, to: 'users#current_user'
|
84
|
+
subscribtion :new_message, to: 'messages#created'
|
84
85
|
end
|
85
86
|
```
|
86
87
|
|
@@ -88,18 +89,54 @@ end
|
|
88
89
|
|
89
90
|
### _module_ options
|
90
91
|
|
91
|
-
|
92
|
+
If you want to want to route everything to controllers, located at `controllers/admin/top_secret`, you can use scope with `module` param:
|
92
93
|
|
93
94
|
```ruby
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
95
|
+
scope module: 'admin/top_secret' do
|
96
|
+
mutation :logIn, to: 'sessions#login' # this will trigger Admin::TopSecret::SessionsController
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
### Named scope
|
98
101
|
|
102
|
+
If you want to nest some routes under some other node, you can use named scope:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
scope :admin do
|
99
106
|
mutation :logIn, to: 'sessions#login' # this will trigger ::SessionsController
|
100
107
|
end
|
101
108
|
```
|
102
109
|
|
110
|
+
This action will be accessible via:
|
111
|
+
|
112
|
+
```graphql
|
113
|
+
mutation {
|
114
|
+
admin {
|
115
|
+
logIn(email: 'john@example.com') { ... }
|
116
|
+
}
|
117
|
+
}
|
118
|
+
```
|
119
|
+
|
120
|
+
## _namespace_
|
121
|
+
|
122
|
+
You may wish to organize groups of controllers under a namespace. Most commonly, you might group a number of administrative controllers under an `Admin::` namespace, and place these controllers under the app/controllers/admin directory. You can route to such a group by using a namespace block:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
namespace :admin do
|
126
|
+
resources :articles, only: :show
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
On GraphQL side, you can reach such route with the following query:
|
131
|
+
|
132
|
+
```graphql
|
133
|
+
query {
|
134
|
+
admin {
|
135
|
+
article(id: '123') { ... }
|
136
|
+
}
|
137
|
+
}
|
138
|
+
```
|
139
|
+
|
103
140
|
## _group_
|
104
141
|
|
105
142
|
You can have multiple routers / schemas. In order to add resources or query only to specific schema, you need wrap it with `group` method, like this:
|
@@ -32,8 +32,8 @@ module GraphqlRails
|
|
32
32
|
[
|
33
33
|
field_name,
|
34
34
|
type_parser.type_arg,
|
35
|
-
|
36
|
-
]
|
35
|
+
description
|
36
|
+
].compact
|
37
37
|
end
|
38
38
|
|
39
39
|
def field_options
|
@@ -41,21 +41,11 @@ module GraphqlRails
|
|
41
41
|
method: property.to_sym,
|
42
42
|
null: optional?,
|
43
43
|
camelize: camelize?,
|
44
|
-
groups: groups
|
44
|
+
groups: groups,
|
45
|
+
**deprecation_reason_params
|
45
46
|
}
|
46
47
|
end
|
47
48
|
|
48
|
-
def argument_args
|
49
|
-
[
|
50
|
-
field_name,
|
51
|
-
type_parser.type_arg,
|
52
|
-
{
|
53
|
-
description: description,
|
54
|
-
required: required?
|
55
|
-
}
|
56
|
-
]
|
57
|
-
end
|
58
|
-
|
59
49
|
protected
|
60
50
|
|
61
51
|
attr_reader :initial_name
|
@@ -65,6 +55,10 @@ module GraphqlRails
|
|
65
55
|
def camelize?
|
66
56
|
options[:input_format] != :original && options[:attribute_name_format] != :original
|
67
57
|
end
|
58
|
+
|
59
|
+
def deprecation_reason_params
|
60
|
+
{ deprecation_reason: deprecation_reason }.compact
|
61
|
+
end
|
68
62
|
end
|
69
63
|
end
|
70
64
|
end
|
@@ -40,6 +40,21 @@ module GraphqlRails
|
|
40
40
|
def optional(new_value = true) # rubocop:disable Style/OptionalBooleanParameter
|
41
41
|
required(!new_value)
|
42
42
|
end
|
43
|
+
|
44
|
+
def deprecated(reason = 'Deprecated')
|
45
|
+
@deprecation_reason = \
|
46
|
+
if [false, nil].include?(reason)
|
47
|
+
nil
|
48
|
+
else
|
49
|
+
reason.is_a?(String) ? reason : 'Deprecated'
|
50
|
+
end
|
51
|
+
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def deprecation_reason
|
56
|
+
@deprecation_reason
|
57
|
+
end
|
43
58
|
end
|
44
59
|
end
|
45
60
|
end
|
@@ -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,14 @@ 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
|
+
**default_value_option,
|
35
|
+
**deprecation_reason_params
|
36
|
+
}
|
29
37
|
end
|
30
38
|
|
31
39
|
def paginated?
|
@@ -36,12 +44,20 @@ module GraphqlRails
|
|
36
44
|
|
37
45
|
attr_reader :initial_name, :config
|
38
46
|
|
47
|
+
def default_value_option
|
48
|
+
{ default_value: default_value }.compact
|
49
|
+
end
|
50
|
+
|
39
51
|
def attribute_name_parser
|
40
52
|
@attribute_name_parser ||= AttributeNameParser.new(
|
41
53
|
initial_name, options: attribute_naming_options
|
42
54
|
)
|
43
55
|
end
|
44
56
|
|
57
|
+
def deprecation_reason_params
|
58
|
+
{ deprecation_reason: deprecation_reason }.compact
|
59
|
+
end
|
60
|
+
|
45
61
|
def attribute_naming_options
|
46
62
|
options.slice(:input_format)
|
47
63
|
end
|
@@ -52,12 +52,6 @@ module GraphqlRails
|
|
52
52
|
GraphQL::InputObjectType
|
53
53
|
].freeze
|
54
54
|
|
55
|
-
PARSEABLE_RAW_GRAPHQL_TYPES = [
|
56
|
-
GraphQL::Schema::Object,
|
57
|
-
GraphQL::Schema::Scalar,
|
58
|
-
GraphQL::Schema::Enum
|
59
|
-
].freeze
|
60
|
-
|
61
55
|
RAW_GRAPHQL_TYPES = (WRAPPER_TYPES + GRAPHQL_BASE_TYPES).freeze
|
62
56
|
|
63
57
|
def unwrapped_scalar_type
|
@@ -103,7 +97,7 @@ module GraphqlRails
|
|
103
97
|
def graphql_type_object?(type_class)
|
104
98
|
return false unless type_class.is_a?(Class)
|
105
99
|
|
106
|
-
|
100
|
+
type_class < GraphQL::Schema::Member
|
107
101
|
end
|
108
102
|
|
109
103
|
def applicable_graphql_type?(type)
|
@@ -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)
|
@@ -62,9 +62,14 @@ module GraphqlRails
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def parameter_filter_class
|
65
|
-
|
65
|
+
if ActiveSupport.gem_version.segments.first < 6
|
66
|
+
return ActiveSupport::ParameterFilter if Object.const_defined?('ActiveSupport::ParameterFilter')
|
66
67
|
|
67
|
-
|
68
|
+
ActionDispatch::Http::ParameterFilter
|
69
|
+
else
|
70
|
+
require 'active_support/parameter_filter'
|
71
|
+
ActiveSupport::ParameterFilter
|
72
|
+
end
|
68
73
|
end
|
69
74
|
end
|
70
75
|
end
|
@@ -12,40 +12,41 @@ module GraphqlRails
|
|
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,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
|
@@ -46,11 +46,7 @@ module GraphqlRails
|
|
46
46
|
|
47
47
|
def find_or_build_dynamic_type(attribute)
|
48
48
|
graphql_model = attribute.graphql_model
|
49
|
-
if graphql_model
|
50
|
-
find_or_build_graphql_model_type(graphql_model)
|
51
|
-
else
|
52
|
-
AddFieldsToGraphqlType.call(klass: klass, attributes: [attribute])
|
53
|
-
end
|
49
|
+
find_or_build_graphql_model_type(graphql_model) if graphql_model
|
54
50
|
end
|
55
51
|
|
56
52
|
def find_or_build_graphql_model_type(graphql_model)
|
@@ -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
|
@@ -28,6 +28,10 @@ module GraphqlRails
|
|
28
28
|
routes << build_mutation(*args, **kwargs)
|
29
29
|
end
|
30
30
|
|
31
|
+
def subscription(*args, **kwargs)
|
32
|
+
routes << build_subscription(*args, **kwargs)
|
33
|
+
end
|
34
|
+
|
31
35
|
private
|
32
36
|
|
33
37
|
attr_reader :autogenerated_action_names, :name, :options
|
@@ -62,6 +66,10 @@ module GraphqlRails
|
|
62
66
|
build_route(QueryRoute, *args, **kwargs)
|
63
67
|
end
|
64
68
|
|
69
|
+
def build_subscription(*args, **kwargs)
|
70
|
+
build_route(SubscriptionRoute, *args, **kwargs)
|
71
|
+
end
|
72
|
+
|
65
73
|
# rubocop:disable Metrics/ParameterLists
|
66
74
|
def build_route(builder, action, prefix: action, suffix: false, on: :member, **custom_options)
|
67
75
|
if suffix == true
|
@@ -6,15 +6,16 @@ module GraphqlRails
|
|
6
6
|
class Router
|
7
7
|
# Generic class for any type graphql action. Should not be used directly
|
8
8
|
class Route
|
9
|
-
attr_reader :name, :module_name, :on, :relative_path, :groups
|
9
|
+
attr_reader :name, :module_name, :on, :relative_path, :groups, :scope_names
|
10
10
|
|
11
|
-
def initialize(name, to: '',
|
11
|
+
def initialize(name, on:, to: '', groups: nil, scope_names: [], **options) # rubocop:disable Metrics/ParameterLists
|
12
12
|
@name = name.to_s.camelize(:lower)
|
13
13
|
@module_name = options[:module].to_s
|
14
14
|
@function = options[:function]
|
15
15
|
@groups = groups
|
16
16
|
@relative_path = to
|
17
17
|
@on = on.to_sym
|
18
|
+
@scope_names = scope_names
|
18
19
|
end
|
19
20
|
|
20
21
|
def path
|