rails-graphql 1.0.0.beta → 1.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/gql_parser.c +1 -16
- data/ext/gql_parser.h +21 -0
- data/ext/shared.c +0 -5
- data/ext/shared.h +6 -6
- data/lib/generators/graphql/channel_generator.rb +27 -0
- data/lib/generators/graphql/controller_generator.rb +9 -4
- data/lib/generators/graphql/install_generator.rb +49 -0
- data/lib/generators/graphql/schema_generator.rb +9 -4
- data/lib/generators/graphql/templates/channel.erb +7 -0
- data/lib/generators/graphql/templates/config.rb +97 -0
- data/lib/generators/graphql/templates/controller.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +5 -3
- data/lib/gql_parser.so +0 -0
- data/lib/rails/graphql/alternative/field_set.rb +12 -0
- data/lib/rails/graphql/alternative/query.rb +13 -8
- data/lib/rails/graphql/alternative/subscription.rb +2 -1
- data/lib/rails/graphql/alternative.rb +4 -0
- data/lib/rails/graphql/argument.rb +5 -3
- data/lib/rails/graphql/callback.rb +10 -8
- data/lib/rails/graphql/collectors/hash_collector.rb +12 -1
- data/lib/rails/graphql/collectors/json_collector.rb +21 -0
- data/lib/rails/graphql/config.rb +86 -59
- data/lib/rails/graphql/directive/include_directive.rb +0 -1
- data/lib/rails/graphql/directive/skip_directive.rb +0 -1
- data/lib/rails/graphql/directive/specified_by_directive.rb +24 -0
- data/lib/rails/graphql/directive.rb +31 -25
- data/lib/rails/graphql/event.rb +7 -6
- data/lib/rails/graphql/field/authorized_field.rb +0 -5
- data/lib/rails/graphql/field/input_field.rb +0 -5
- data/lib/rails/graphql/field/mutation_field.rb +5 -6
- data/lib/rails/graphql/field/output_field.rb +13 -2
- data/lib/rails/graphql/field/proxied_field.rb +6 -6
- data/lib/rails/graphql/field/resolved_field.rb +1 -1
- data/lib/rails/graphql/field/subscription_field.rb +35 -52
- data/lib/rails/graphql/field/typed_field.rb +26 -2
- data/lib/rails/graphql/field.rb +20 -19
- data/lib/rails/graphql/global_id.rb +5 -1
- data/lib/rails/graphql/helpers/inherited_collection/array.rb +1 -0
- data/lib/rails/graphql/helpers/inherited_collection/base.rb +3 -1
- data/lib/rails/graphql/helpers/inherited_collection/hash.rb +2 -1
- data/lib/rails/graphql/helpers/registerable.rb +1 -1
- data/lib/rails/graphql/helpers/with_arguments.rb +3 -2
- data/lib/rails/graphql/helpers/with_assignment.rb +5 -5
- data/lib/rails/graphql/helpers/with_callbacks.rb +3 -3
- data/lib/rails/graphql/helpers/with_description.rb +10 -8
- data/lib/rails/graphql/helpers/with_directives.rb +5 -1
- data/lib/rails/graphql/helpers/with_events.rb +1 -0
- data/lib/rails/graphql/helpers/with_fields.rb +30 -24
- data/lib/rails/graphql/helpers/with_name.rb +3 -2
- data/lib/rails/graphql/helpers/with_schema_fields.rb +75 -51
- data/lib/rails/graphql/introspection.rb +1 -1
- data/lib/rails/graphql/railtie.rb +3 -2
- data/lib/rails/graphql/railties/app/base_channel.rb +10 -0
- data/lib/rails/graphql/railties/app/base_controller.rb +12 -0
- data/lib/rails/graphql/railties/app/views/_cable.js.erb +56 -0
- data/lib/rails/graphql/railties/app/views/_fetch.js.erb +20 -0
- data/lib/rails/graphql/railties/app/views/graphiql.html.erb +101 -0
- data/lib/rails/graphql/railties/base_generator.rb +3 -9
- data/lib/rails/graphql/railties/channel.rb +8 -8
- data/lib/rails/graphql/railties/controller.rb +51 -26
- data/lib/rails/graphql/request/arguments.rb +2 -1
- data/lib/rails/graphql/request/backtrace.rb +31 -10
- data/lib/rails/graphql/request/component/field.rb +15 -8
- data/lib/rails/graphql/request/component/fragment.rb +13 -7
- data/lib/rails/graphql/request/component/operation/subscription.rb +4 -6
- data/lib/rails/graphql/request/component/operation.rb +12 -5
- data/lib/rails/graphql/request/component/spread.rb +13 -4
- data/lib/rails/graphql/request/component/typename.rb +1 -1
- data/lib/rails/graphql/request/component.rb +2 -0
- data/lib/rails/graphql/request/context.rb +1 -1
- data/lib/rails/graphql/request/event.rb +6 -2
- data/lib/rails/graphql/request/helpers/directives.rb +1 -0
- data/lib/rails/graphql/request/helpers/selection_set.rb +10 -4
- data/lib/rails/graphql/request/helpers/value_writers.rb +8 -5
- data/lib/rails/graphql/request/prepared_data.rb +3 -1
- data/lib/rails/graphql/request/steps/organizable.rb +1 -1
- data/lib/rails/graphql/request/steps/preparable.rb +1 -1
- data/lib/rails/graphql/request/steps/resolvable.rb +1 -1
- data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +3 -3
- data/lib/rails/graphql/request/strategy.rb +18 -4
- data/lib/rails/graphql/request/subscription.rb +18 -16
- data/lib/rails/graphql/request.rb +71 -41
- data/lib/rails/graphql/schema.rb +39 -86
- data/lib/rails/graphql/shortcuts.rb +11 -5
- data/lib/rails/graphql/source/active_record/builders.rb +22 -24
- data/lib/rails/graphql/source/active_record_source.rb +96 -34
- data/lib/rails/graphql/source/base.rb +13 -40
- data/lib/rails/graphql/source/builder.rb +14 -22
- data/lib/rails/graphql/source/scoped_arguments.rb +10 -4
- data/lib/rails/graphql/source.rb +31 -38
- data/lib/rails/graphql/subscription/provider/action_cable.rb +10 -9
- data/lib/rails/graphql/subscription/provider/base.rb +6 -5
- data/lib/rails/graphql/subscription/store/base.rb +5 -9
- data/lib/rails/graphql/subscription/store/memory.rb +18 -9
- data/lib/rails/graphql/type/creator.rb +198 -0
- data/lib/rails/graphql/type/enum.rb +17 -9
- data/lib/rails/graphql/type/input.rb +30 -7
- data/lib/rails/graphql/type/interface.rb +15 -4
- data/lib/rails/graphql/type/object/directive_object.rb +6 -5
- data/lib/rails/graphql/type/object/input_value_object.rb +3 -4
- data/lib/rails/graphql/type/object/type_object.rb +40 -13
- data/lib/rails/graphql/type/object.rb +11 -6
- data/lib/rails/graphql/type/scalar/binary_scalar.rb +2 -0
- data/lib/rails/graphql/type/scalar/date_scalar.rb +2 -0
- data/lib/rails/graphql/type/scalar/date_time_scalar.rb +2 -0
- data/lib/rails/graphql/type/scalar/decimal_scalar.rb +2 -0
- data/lib/rails/graphql/type/scalar/json_scalar.rb +3 -1
- data/lib/rails/graphql/type/scalar/time_scalar.rb +3 -1
- data/lib/rails/graphql/type/scalar.rb +2 -2
- data/lib/rails/graphql/type/union.rb +7 -2
- data/lib/rails/graphql/type.rb +10 -2
- data/lib/rails/graphql/type_map.rb +20 -7
- data/lib/rails/graphql/uri.rb +5 -4
- data/lib/rails/graphql/version.rb +6 -2
- data/lib/rails/graphql.rb +11 -8
- data/test/assets/introspection-mem.txt +1 -1
- data/test/assets/introspection.gql +2 -0
- data/test/assets/mem.gql +74 -60
- data/test/assets/mysql.gql +69 -55
- data/test/assets/sqlite.gql +78 -64
- data/test/assets/translate.gql +50 -39
- data/test/config.rb +2 -1
- data/test/graphql/schema_test.rb +2 -31
- data/test/graphql/source_test.rb +1 -11
- data/test/graphql/type/interface_test.rb +8 -5
- data/test/graphql/type/object_test.rb +8 -2
- data/test/graphql/type_map_test.rb +13 -16
- data/test/integration/global_id_test.rb +4 -4
- data/test/integration/memory/star_wars_validation_test.rb +2 -2
- data/test/integration/mysql/star_wars_introspection_test.rb +1 -1
- data/test/integration/resolver_precedence_test.rb +1 -1
- data/test/integration/schemas/memory.rb +3 -4
- data/test/integration/sqlite/star_wars_global_id_test.rb +27 -21
- data/test/integration/sqlite/star_wars_introspection_test.rb +1 -1
- data/test/integration/translate_test.rb +26 -14
- metadata +22 -9
@@ -39,7 +39,7 @@ module Rails
|
|
39
39
|
stack = [component] + request.stack
|
40
40
|
counter = stack.count { |item| !item.is_a?(Numeric) }
|
41
41
|
objects = request.strategy.context
|
42
|
-
|
42
|
+
oid = -1
|
43
43
|
|
44
44
|
last_object = suffix = nil
|
45
45
|
while (item = stack.shift)
|
@@ -51,11 +51,13 @@ module Rails
|
|
51
51
|
add = send("row_for_#{item.kind}", data, item, suffix)
|
52
52
|
|
53
53
|
if item.kind == :field
|
54
|
-
|
55
|
-
data[5] ||=
|
56
|
-
data[3] ||= print_object(objects&.at(
|
54
|
+
oid += 1
|
55
|
+
data[5] ||= oid == 0 ? '↓' : print_object(objects&.at(oid - 1))
|
56
|
+
data[3] ||= print_object(objects&.at(oid))
|
57
57
|
end
|
58
58
|
|
59
|
+
data[4] = clean_arguments(data[4], request) if data[4]
|
60
|
+
|
59
61
|
suffix = nil
|
60
62
|
counter -= 1
|
61
63
|
add_to_table(table, data) if add != false
|
@@ -67,7 +69,7 @@ module Rails
|
|
67
69
|
# Print the backtrace steps of the error
|
68
70
|
def print_backtrace(error, request)
|
69
71
|
steps = error.backtrace
|
70
|
-
steps = cleaner.clean(steps) unless cleaner.nil?
|
72
|
+
# steps = cleaner.clean(steps) unless cleaner.nil?
|
71
73
|
|
72
74
|
klass = +"(\e[4m#{error.class}\e[24m)"
|
73
75
|
stage = +" [#{request.strategy.stage}]" if skip_base_class != StandardError
|
@@ -122,26 +124,45 @@ module Rails
|
|
122
124
|
object.respond_to?(:to_gql_backtrace) ? object.to_gql_backtrace : object.inspect
|
123
125
|
end
|
124
126
|
|
127
|
+
# Make sure to properly parse arguments and filter them
|
128
|
+
def clean_arguments(arguments, request)
|
129
|
+
value = arguments.as_json
|
130
|
+
return '{}' if value.blank?
|
131
|
+
|
132
|
+
request.cache(:backtrace_arguments_filter) do
|
133
|
+
ActiveSupport::ParameterFilter.new(GraphQL.config.filter_parameters)
|
134
|
+
end.filter(value)
|
135
|
+
end
|
136
|
+
|
125
137
|
# Visitors
|
126
138
|
def row_for_field(data, item, suffix)
|
127
139
|
field = item.field
|
128
|
-
|
140
|
+
parent =
|
141
|
+
if !field
|
142
|
+
'*'
|
143
|
+
elsif field.owner.is_a?(Helpers::WithSchemaFields)
|
144
|
+
item.request.schema.type_name_for(field.schema_type)
|
145
|
+
else
|
146
|
+
field.owner.gql_name
|
147
|
+
end
|
148
|
+
|
149
|
+
name = +"#{parent}.#{field.gql_name}#{suffix}" unless field.nil?
|
129
150
|
|
130
151
|
data.push(name || +"*.#{item.name}")
|
131
|
-
data.push(nil,
|
152
|
+
data.push(nil, item.arguments, nil)
|
132
153
|
end
|
133
154
|
|
134
155
|
def row_for_fragment(data, item, *)
|
135
156
|
type = item.instance_variable_get(:@node)[1]
|
136
157
|
object = item.current_object || item.type_klass
|
137
|
-
data.push(+"fragment #{item.name}", type,
|
158
|
+
data.push(+"fragment #{item.name}", type, nil)
|
138
159
|
data.push(print_object(object))
|
139
160
|
end
|
140
161
|
|
141
162
|
def row_for_operation(data, item, *)
|
142
163
|
data.push(+"#{item.type} #{item.name}".squish)
|
143
164
|
data.push('nil')
|
144
|
-
data.push(item.
|
165
|
+
data.push(item.variables)
|
145
166
|
data.push(item.typename)
|
146
167
|
end
|
147
168
|
|
@@ -150,7 +171,7 @@ module Rails
|
|
150
171
|
|
151
172
|
type = item.instance_variable_get(:@node)[1]
|
152
173
|
object = item.current_object || item.type_klass
|
153
|
-
data.push('...', type,
|
174
|
+
data.push('...', type, nil)
|
154
175
|
data.push(print_object(object))
|
155
176
|
end
|
156
177
|
|
@@ -15,7 +15,7 @@ module Rails
|
|
15
15
|
|
16
16
|
delegate :decorate, to: :type_klass
|
17
17
|
delegate :operation, :variables, :request, to: :parent
|
18
|
-
delegate :method_name, :resolver, :performer, :type_klass
|
18
|
+
delegate :method_name, :resolver, :performer, :type_klass, :leaf_type?,
|
19
19
|
:dynamic_resolver?, :mutation?, to: :field
|
20
20
|
|
21
21
|
attr_reader :name, :alias_name, :parent, :field, :arguments, :current_object
|
@@ -97,10 +97,11 @@ module Rails
|
|
97
97
|
(try(:current_object) || try(:type_klass))&.gql_name
|
98
98
|
end
|
99
99
|
|
100
|
-
# Check if the field is an entry point, meaning that
|
101
|
-
#
|
100
|
+
# Check if the field is an entry point, meaning that it is attached to
|
101
|
+
# an owner that has Schema Fields
|
102
102
|
def entry_point?
|
103
|
-
|
103
|
+
return @entry_point if defined?(@entry_point)
|
104
|
+
@entry_point = field.entry_point?
|
104
105
|
end
|
105
106
|
|
106
107
|
# Fields are assignable because they are actually the selection, so they
|
@@ -117,6 +118,12 @@ module Rails
|
|
117
118
|
value != false
|
118
119
|
end
|
119
120
|
|
121
|
+
# Override this to also check if the key would be added to the response
|
122
|
+
# again
|
123
|
+
def skipped?
|
124
|
+
super || response.key?(gql_name)
|
125
|
+
end
|
126
|
+
|
120
127
|
# A little extension of the +is_a?+ method that allows checking it using
|
121
128
|
# the underlying +field+
|
122
129
|
def of_type?(klass)
|
@@ -150,6 +157,7 @@ module Rails
|
|
150
157
|
end
|
151
158
|
|
152
159
|
# Build the cache object
|
160
|
+
# TODO: Add the arguments into the GID, but the problem is variables
|
153
161
|
def cache_dump
|
154
162
|
super.merge(field: (field && all_to_gid(field)))
|
155
163
|
end
|
@@ -172,10 +180,10 @@ module Rails
|
|
172
180
|
check_assignment!
|
173
181
|
|
174
182
|
parse_directives(@node[3])
|
175
|
-
check_authorization!
|
176
|
-
|
177
183
|
parse_arguments(@node[2])
|
178
184
|
parse_selection(@node[4])
|
185
|
+
|
186
|
+
check_authorization!
|
179
187
|
end
|
180
188
|
end
|
181
189
|
|
@@ -221,8 +229,7 @@ module Rails
|
|
221
229
|
# Check if the field was assigned correctly to an output field
|
222
230
|
def check_assignment!
|
223
231
|
raise MissingFieldError, (+<<~MSG).squish if field.nil?
|
224
|
-
Unable to find a field named "#{gql_name}" on
|
225
|
-
#{entry_point? ? operation.kind : parent.type_klass.name}.
|
232
|
+
Unable to find a field named "#{gql_name}" on #{parent.typename}.
|
226
233
|
MSG
|
227
234
|
|
228
235
|
raise FieldError, (+<<~MSG).squish unless field.output_type?
|
@@ -23,6 +23,16 @@ module Rails
|
|
23
23
|
check_duplicated_fragment!
|
24
24
|
end
|
25
25
|
|
26
|
+
# Check if all the sub fields are broadcastable
|
27
|
+
def broadcastable?
|
28
|
+
selection.each_value.all?(&:broadcastable?)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Check if the fragment has been prepared already
|
32
|
+
def prepared?
|
33
|
+
defined?(@prepared) && @prepared
|
34
|
+
end
|
35
|
+
|
26
36
|
# Return a lazy loaded variable proc
|
27
37
|
def variables
|
28
38
|
Request::Arguments.lazy
|
@@ -59,11 +69,6 @@ module Rails
|
|
59
69
|
@current_object = nil
|
60
70
|
end
|
61
71
|
|
62
|
-
# Check if all the sub fields are broadcastable
|
63
|
-
def broadcastable?
|
64
|
-
selection.each_value.all?(&:broadcastable?)
|
65
|
-
end
|
66
|
-
|
67
72
|
# Build the cache object
|
68
73
|
def cache_dump
|
69
74
|
super.merge(type_klass: all_to_gid(type_klass))
|
@@ -105,8 +110,9 @@ module Rails
|
|
105
110
|
def resolve
|
106
111
|
return if unresolvable?
|
107
112
|
|
108
|
-
|
109
|
-
|
113
|
+
type = type_klass
|
114
|
+
object = @current_object
|
115
|
+
resolve_then if (object.nil? && type&.operational?) || type =~ object
|
110
116
|
end
|
111
117
|
|
112
118
|
# This will just trigger the selection resolver
|
@@ -89,14 +89,12 @@ module Rails
|
|
89
89
|
|
90
90
|
# Rewrite this method so that the subscription can be generated in
|
91
91
|
# the right place
|
92
|
-
def resolve_then(
|
93
|
-
|
92
|
+
def resolve_then(&block)
|
93
|
+
super do
|
94
94
|
save_subscription
|
95
|
-
trigger_event(:subscribed)
|
96
|
-
|
95
|
+
trigger_event(:subscribed, subscription: subscription)
|
96
|
+
block.call if block.present?
|
97
97
|
end
|
98
|
-
|
99
|
-
super(subscribe_block, &block)
|
100
98
|
end
|
101
99
|
|
102
100
|
# Save the subscription using the schema subscription provider
|
@@ -18,7 +18,7 @@ module Rails
|
|
18
18
|
|
19
19
|
# Helper method to initialize an operation given the node
|
20
20
|
def build(request, node)
|
21
|
-
request.build(const_get(node.type.to_s.classify), request, node)
|
21
|
+
request.build(const_get(node.type.to_s.classify, false), request, node)
|
22
22
|
end
|
23
23
|
|
24
24
|
# Rewrite the kind to always return +:operation+
|
@@ -73,6 +73,13 @@ module Rails
|
|
73
73
|
schema.fields_for(type)
|
74
74
|
end
|
75
75
|
|
76
|
+
# Allow accessing the fake type form the schema. It's used for
|
77
|
+
# inline spreads without a specified type
|
78
|
+
def type_klass
|
79
|
+
return @type_klass if defined?(@type_klass)
|
80
|
+
@type_klass = schema.public_send("#{type}_type")
|
81
|
+
end
|
82
|
+
|
76
83
|
# The typename is always based on the fake name used for the set of
|
77
84
|
# schema fields
|
78
85
|
def typename
|
@@ -109,7 +116,7 @@ module Rails
|
|
109
116
|
return super unless defined?(@used_fragments)
|
110
117
|
|
111
118
|
super ^ used_fragments.reduce(0) do |value, fragment|
|
112
|
-
request.fragments[fragment].hash
|
119
|
+
value ^ request.fragments[fragment].hash
|
113
120
|
end
|
114
121
|
end
|
115
122
|
|
@@ -129,8 +136,8 @@ module Rails
|
|
129
136
|
|
130
137
|
# Trigger an specific event with the +type+ of the operation
|
131
138
|
def organize
|
139
|
+
trigger_event(type)
|
132
140
|
organize_then do
|
133
|
-
trigger_event(type)
|
134
141
|
yield if block_given?
|
135
142
|
organize_fields
|
136
143
|
report_unused_variables
|
@@ -147,8 +154,8 @@ module Rails
|
|
147
154
|
end
|
148
155
|
|
149
156
|
# Resolve all the fields
|
150
|
-
def
|
151
|
-
|
157
|
+
def resolve_then(&block)
|
158
|
+
super(block) { resolve_fields }
|
152
159
|
end
|
153
160
|
|
154
161
|
# Don't stack over response when the operation doesn't have a name
|
@@ -95,7 +95,7 @@ module Rails
|
|
95
95
|
def organize_then(&block)
|
96
96
|
super(block) do
|
97
97
|
if inline?
|
98
|
-
@type_klass = find_type!(@node[1])
|
98
|
+
@type_klass = @node[1].nil? ? parent.type_klass : find_type!(@node[1])
|
99
99
|
parse_directives(@node[2])
|
100
100
|
parse_selection(@node[3])
|
101
101
|
else
|
@@ -111,17 +111,26 @@ module Rails
|
|
111
111
|
# Spread has a special behavior when using a fragment
|
112
112
|
def prepare
|
113
113
|
return super if inline?
|
114
|
-
|
114
|
+
catch(:fragment_prepared) do
|
115
|
+
return fragment.prepare!
|
116
|
+
end
|
117
|
+
|
118
|
+
invalidate!
|
119
|
+
raise ExecutionError, (+<<~MSG).squish
|
120
|
+
Fields inside the "#{name}" fragment tried to be prepared more than once.
|
121
|
+
This feature is not supported yet.
|
122
|
+
MSG
|
115
123
|
end
|
116
124
|
|
117
125
|
# Resolve the spread operation
|
118
126
|
def resolve
|
119
127
|
return if unresolvable?
|
120
128
|
|
121
|
-
object = (defined?(@current_object) && @current_object)
|
129
|
+
object = (defined?(@current_object) && @current_object)
|
130
|
+
object ||= parent.type_klass unless parent.kind == :operation
|
122
131
|
return run_on_fragment(:resolve_with!, object) unless inline?
|
123
132
|
|
124
|
-
super if type_klass =~ object
|
133
|
+
super if (object.nil? && type_klass&.operational?) || type_klass =~ object
|
125
134
|
end
|
126
135
|
|
127
136
|
# This will just trigger the selection resolver
|
@@ -45,7 +45,7 @@ module Rails
|
|
45
45
|
|
46
46
|
# Write the typename information
|
47
47
|
def write_value(value)
|
48
|
-
response.serialize(Type::Scalar::StringScalar, gql_name, value)
|
48
|
+
response.serialize(Type::Scalar::StringScalar, gql_name, value.itself)
|
49
49
|
end
|
50
50
|
|
51
51
|
# Typename is always broadcastable
|
@@ -111,6 +111,8 @@ module Rails
|
|
111
111
|
# Run a given block and ensure to capture exceptions to set them as
|
112
112
|
# errors
|
113
113
|
def report_exception(error)
|
114
|
+
return if request.rescue_with_handler(error, source: self) == false
|
115
|
+
|
114
116
|
Backtrace.print(error, self, request)
|
115
117
|
|
116
118
|
stack_path = request.stack_to_path
|
@@ -10,10 +10,10 @@ module Rails
|
|
10
10
|
class Event < GraphQL::Event
|
11
11
|
OBJECT_BASED_READERS = %i[fragment spread].freeze
|
12
12
|
|
13
|
-
delegate :errors, :context, to: :request
|
13
|
+
delegate :errors, :context, :extensions, to: :request
|
14
14
|
delegate :instance_for, to: :strategy
|
15
15
|
delegate :memo, :schema, to: :source
|
16
|
-
delegate :
|
16
|
+
delegate :subscription_provider, to: :schema
|
17
17
|
|
18
18
|
attr_reader :strategy, :request, :index
|
19
19
|
|
@@ -34,12 +34,16 @@ module Rails
|
|
34
34
|
super(name, source, **data)
|
35
35
|
end
|
36
36
|
|
37
|
+
# TODO: Implement a faster way to check if if the event is from the
|
38
|
+
# same source by separating exclusive events beforehand
|
39
|
+
|
37
40
|
# If the source is a field, than also compare to the actual field
|
38
41
|
def same_source?(other)
|
39
42
|
super || (source.try(:kind) == :field && source.field == other)
|
40
43
|
end
|
41
44
|
|
42
45
|
# Provide a way to access the current field value
|
46
|
+
# TODO: Maybe change this to +current+ to get the value by reference
|
43
47
|
def current_value
|
44
48
|
resolver&.current_value
|
45
49
|
end
|
@@ -55,7 +55,7 @@ module Rails
|
|
55
55
|
# Using +fields_source+, find the needed ones to be assigned to the
|
56
56
|
# current requested fields. As shown by benchmark, since the index is
|
57
57
|
# based on Symbols, the best way to find +gql_name+ based fields is
|
58
|
-
# through iteration and
|
58
|
+
# through iteration, then search and assign. Complexity O(n)
|
59
59
|
def assign_fields!(assigners)
|
60
60
|
pending = assigners.map(&:size).reduce(:+) || 0
|
61
61
|
return if pending.zero?
|
@@ -72,19 +72,21 @@ module Rails
|
|
72
72
|
# Recursive operation that perform the organization step for the
|
73
73
|
# selection
|
74
74
|
def organize_fields
|
75
|
-
|
75
|
+
return unless run_selection?
|
76
|
+
selection.each_value(&:organize!)
|
76
77
|
end
|
77
78
|
|
78
79
|
# Find all the fields that have a prepare step and execute them
|
79
80
|
def prepare_fields
|
80
|
-
|
81
|
+
return unless run_selection?
|
82
|
+
selection.each_value(&:prepare!)
|
81
83
|
end
|
82
84
|
|
83
85
|
# Trigger the process of resolving the value of all the fields. Since
|
84
86
|
# complex object may or may not be inside an array, this helps to
|
85
87
|
# decide if a new stack should be started or not
|
86
88
|
def resolve_fields(object = nil)
|
87
|
-
return unless
|
89
|
+
return unless run_selection?
|
88
90
|
|
89
91
|
items = selection.each_value
|
90
92
|
items = items.each_with_object(object) unless object.nil?
|
@@ -96,6 +98,10 @@ module Rails
|
|
96
98
|
|
97
99
|
private
|
98
100
|
|
101
|
+
def run_selection?
|
102
|
+
selection.present? && !unresolvable?
|
103
|
+
end
|
104
|
+
|
99
105
|
def add_component(node)
|
100
106
|
item_name = node[1] || node[0]
|
101
107
|
|
@@ -20,6 +20,8 @@ module Rails
|
|
20
20
|
|
21
21
|
# Resolve a given value when it is an array
|
22
22
|
def write_array(value, idx = -1, &block)
|
23
|
+
return write_leaf(value) if value.nil?
|
24
|
+
|
23
25
|
write_array!(value) do |item|
|
24
26
|
stacked(idx += 1) do
|
25
27
|
block.call(item, idx)
|
@@ -30,16 +32,17 @@ module Rails
|
|
30
32
|
block.call(nil, idx)
|
31
33
|
response.next
|
32
34
|
|
33
|
-
|
35
|
+
format_array_exception(error, idx)
|
34
36
|
request.exception_to_error(error, self)
|
35
37
|
end
|
36
38
|
rescue StandardError => error
|
37
|
-
|
39
|
+
format_array_exception(error, idx)
|
38
40
|
raise
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
44
|
# Helper to start writing as array
|
45
|
+
# TODO: Add the support for `iterator`
|
43
46
|
def write_array!(value, &block)
|
44
47
|
raise InvalidValueError, (+<<~MSG).squish unless value.respond_to?(:each)
|
45
48
|
The #{gql_name} field is excepting an array
|
@@ -55,7 +58,7 @@ module Rails
|
|
55
58
|
end
|
56
59
|
|
57
60
|
# Add the item index to the exception message
|
58
|
-
def
|
61
|
+
def format_array_exception(error, idx)
|
59
62
|
real_error = (+<<~MSG).squish
|
60
63
|
The #{ActiveSupport::Inflector.ordinalize(idx + 1)} value of the #{gql_name} field
|
61
64
|
MSG
|
@@ -70,13 +73,13 @@ module Rails
|
|
70
73
|
|
71
74
|
# Write a value based on a Union type
|
72
75
|
def write_union(value)
|
73
|
-
object = type_klass.
|
76
|
+
object = type_klass.type_for(value, request)
|
74
77
|
object.nil? ? raise_invalid_member! : resolve_fields(object)
|
75
78
|
end
|
76
79
|
|
77
80
|
# Write a value based on a Interface type
|
78
81
|
def write_interface(value)
|
79
|
-
object = type_klass.
|
82
|
+
object = type_klass.type_for(value, request)
|
80
83
|
object.nil? ? raise_invalid_member! : resolve_fields(object)
|
81
84
|
end
|
82
85
|
|
@@ -17,6 +17,7 @@ module Rails
|
|
17
17
|
REPEAT_OPTIONS = {
|
18
18
|
true => true,
|
19
19
|
false => 1,
|
20
|
+
once: 1,
|
20
21
|
cycle: true,
|
21
22
|
always: true,
|
22
23
|
}.freeze
|
@@ -55,7 +56,8 @@ module Rails
|
|
55
56
|
@field = field
|
56
57
|
@value = value
|
57
58
|
@array = value.is_a?(Array) && !field.array?
|
58
|
-
@repeat =
|
59
|
+
@repeat = true if !@array && !value.is_a?(Array)
|
60
|
+
@repeat ||=
|
59
61
|
case repeat
|
60
62
|
when Numeric then repeat
|
61
63
|
when Enumerator then repeat.size
|
@@ -16,9 +16,9 @@ module Rails
|
|
16
16
|
def resolve!
|
17
17
|
response.with_stack('data') do
|
18
18
|
for_each_operation do |op|
|
19
|
-
collect_listeners
|
20
|
-
collect_data(
|
21
|
-
collect_response
|
19
|
+
collect_listeners { op.organize! }
|
20
|
+
collect_data(op.mutation?) { op.prepare! }
|
21
|
+
collect_response { op.resolve! }
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -92,6 +92,7 @@ module Rails
|
|
92
92
|
# Execute the prepare step for the given +field+ and execute the given
|
93
93
|
# block using context stack
|
94
94
|
def prepare(field, &block)
|
95
|
+
check_fragment_multiple_prepare!(field)
|
95
96
|
value = safe_store_data(field) do
|
96
97
|
prepared = request.prepared_data_for(field)
|
97
98
|
if prepared.is_a?(PreparedData)
|
@@ -131,7 +132,7 @@ module Rails
|
|
131
132
|
|
132
133
|
if field.try(:dynamic_resolver?)
|
133
134
|
prepared = prepared_data_for(field)
|
134
|
-
args << Event.trigger(:resolve, field, self,
|
135
|
+
args << Event.trigger(:resolve, field, self, prepared_data: prepared, &field.resolver)
|
135
136
|
elsif field.prepared_data?
|
136
137
|
args << prepared_data_for(field)
|
137
138
|
else
|
@@ -279,6 +280,9 @@ module Rails
|
|
279
280
|
@context = request.build(Request::Context)
|
280
281
|
|
281
282
|
# TODO: Create an orchestrator to allow cross query loading
|
283
|
+
# TODO: We don't need to traverse over the fields, we can
|
284
|
+
# get the ones with such event and use parent to figure out
|
285
|
+
# the stack
|
282
286
|
yield if force || listening_to?(:prepare)
|
283
287
|
end
|
284
288
|
|
@@ -290,14 +294,24 @@ module Rails
|
|
290
294
|
|
291
295
|
# Fetch the data for a given field and set as the first element
|
292
296
|
# of the returned list
|
297
|
+
# TODO: Maybe implement root value to be returned by entry points
|
293
298
|
def data_for(result, field)
|
294
299
|
return result << @data_pool[field] if @data_pool.key?(field)
|
295
300
|
return if field.entry_point?
|
296
301
|
|
297
|
-
|
298
|
-
|
302
|
+
key = field.method_name
|
303
|
+
if (current = context.current_value).is_a?(::Hash)
|
304
|
+
result << (current.key?(key) ? current[key] : current[field.gql_name])
|
305
|
+
elsif current.respond_to?(key)
|
306
|
+
result << current.public_send(key)
|
307
|
+
end
|
308
|
+
end
|
299
309
|
|
300
|
-
|
310
|
+
# If the data pool already have data for the given +field+ and there
|
311
|
+
# is a fragment in the stack, we throw back to the fragment
|
312
|
+
def check_fragment_multiple_prepare!(field)
|
313
|
+
return unless @data_pool.key?(field)
|
314
|
+
throw(:fragment_prepared) if request.stack.any?(Component::Fragment)
|
301
315
|
end
|
302
316
|
end
|
303
317
|
end
|
@@ -6,34 +6,36 @@ module Rails
|
|
6
6
|
# = GraphQL Request Subscription
|
7
7
|
#
|
8
8
|
# A simple object to store information about a generated subscription
|
9
|
-
# TODO:
|
10
|
-
# before saving it into a subscription
|
9
|
+
# TODO: Maybe add a callback for the schema allowing it to prepare the
|
10
|
+
# context before saving it into a subscription
|
11
11
|
class Subscription
|
12
12
|
NULL_SCOPE = Object.new.freeze
|
13
13
|
|
14
14
|
attr_reader :sid, :schema, :args, :field, :scope, :context, :broadcastable,
|
15
|
-
:origin, :
|
15
|
+
:origin, :created_at, :updated_at, :operation_id
|
16
16
|
|
17
17
|
alias broadcastable? broadcastable
|
18
|
+
alias id sid
|
18
19
|
|
19
20
|
def initialize(request, operation)
|
20
|
-
entrypoint = operation.selection.each_value.first
|
21
|
+
entrypoint = operation.selection.each_value.first # Memory Fingerprint
|
21
22
|
|
22
|
-
@schema = request.schema.namespace
|
23
|
-
@origin = request.origin
|
24
|
-
@operation_id = operation.hash
|
25
|
-
@args = entrypoint.arguments.to_h
|
26
|
-
@field = entrypoint.field
|
27
|
-
@context = request.context.to_h
|
28
|
-
@broadcastable = operation.broadcastable?
|
29
|
-
|
23
|
+
@schema = request.schema.namespace # 1 Symbol
|
24
|
+
@origin = request.origin # * HEAVY!
|
25
|
+
@operation_id = operation.hash # 1 Integer
|
26
|
+
@args = entrypoint.arguments.to_h # 1 Hash of GQL values
|
27
|
+
@field = entrypoint.field # 1 Pointer
|
28
|
+
@context = request.context.to_h # 1 Hash +/- heavy
|
29
|
+
@broadcastable = operation.broadcastable? # 1 Boolean
|
30
|
+
@created_at = Time.current # 1 Integer
|
31
|
+
updated! # 1 Integer
|
30
32
|
|
31
|
-
@scope = parse_scope(field.full_scope, request, operation)
|
32
|
-
@sid = request.schema.subscription_id_for(self)
|
33
|
+
@scope = parse_scope(field.full_scope, request, operation) # 1 Integer after save
|
34
|
+
@sid = request.schema.subscription_id_for(self) # 1 String
|
33
35
|
end
|
34
36
|
|
35
37
|
def updated!
|
36
|
-
@
|
38
|
+
@updated_at = Time.current
|
37
39
|
end
|
38
40
|
|
39
41
|
def marshal_dump
|
@@ -44,7 +46,7 @@ module Rails
|
|
44
46
|
(+<<~INFO).squish << '>'
|
45
47
|
#<#{self.class.name}
|
46
48
|
#{schema}@#{sid}
|
47
|
-
[#{scope.
|
49
|
+
[#{scope.inspect}, #{args.hash}]
|
48
50
|
#{field.inspect}
|
49
51
|
INFO
|
50
52
|
end
|