rails-graphql 1.0.0.beta → 1.0.0.rc2
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/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
|