Class: Rails::GraphQL::Request
- Inherits:
-
Object
- Object
- Rails::GraphQL::Request
- Extended by:
- ActiveSupport::Autoload
- Defined in:
- lib/rails/graphql/request.rb,
lib/rails/graphql/request/event.rb,
lib/rails/graphql/request/errors.rb,
lib/rails/graphql/request/context.rb,
lib/rails/graphql/request/strategy.rb,
lib/rails/graphql/request/arguments.rb,
lib/rails/graphql/request/backtrace.rb,
lib/rails/graphql/request/component.rb,
lib/rails/graphql/request/subscription.rb,
lib/rails/graphql/request/prepared_data.rb,
lib/rails/graphql/request/component/field.rb,
lib/rails/graphql/request/component/spread.rb,
lib/rails/graphql/request/steps/preparable.rb,
lib/rails/graphql/request/steps/resolvable.rb,
lib/rails/graphql/request/steps/organizable.rb,
lib/rails/graphql/request/component/fragment.rb,
lib/rails/graphql/request/component/typename.rb,
lib/rails/graphql/request/helpers/directives.rb,
lib/rails/graphql/request/steps/authorizable.rb,
lib/rails/graphql/request/component/operation.rb,
lib/rails/graphql/request/helpers/selection_set.rb,
lib/rails/graphql/request/helpers/value_writers.rb,
lib/rails/graphql/request/strategy/cached_strategy.rb,
lib/rails/graphql/request/strategy/dynamic_instance.rb,
lib/rails/graphql/request/strategy/sequenced_strategy.rb,
lib/rails/graphql/request/strategy/multi_query_strategy.rb,
lib/rails/graphql/request/component/operation/subscription.rb
Overview
GraphQL Request
This class is responsible for processing a GraphQL response. It will handle queries, mutations, and subscription, as long as all of them are provided together. It also can be executed multiple times using the same context calling execute multiple times.
Options
-
:args- The arguments of the request, same as variables -
:as- The format of the output of the request, supports both:hashand:string(defaults to :string) -
:context- The context of the request, which can be accessed in fields, resolvers and so as a way to customize the result -
:controller- From which controller this operation is running from, which provides view-like access to helpers and methods through the request -
:namespace- Set what is the namespace used for the request (defaults to :base) -
:operation_name- The name of the operation as a sort of label, it can also be collected by the name of the single operation in the request -
:schema- The schema on which the request should run on. It has higher precedence than the namespace -
:variables- The variables of the request
Defined Under Namespace
Modules: Authorizable, Backtrace, Directives, Organizable, Preparable, Resolvable, SelectionSet, ValueWriters Classes: Arguments, Component, Context, Errors, Event, PreparedData, Strategy, Subscription
Constant Summary collapse
- RESPONSE_FORMATS =
{ string: :to_json, object: :as_json, json: :as_json, hash: :as_json, }.freeze
Instance Attribute Summary collapse
-
#args ⇒ Object
(also: #arguments)
readonly
Returns the value of attribute args.
-
#document ⇒ Object
readonly
Returns the value of attribute document.
-
#errors ⇒ Object
readonly
Returns the value of attribute errors.
-
#fragments ⇒ Object
readonly
Returns the value of attribute fragments.
-
#operation_name ⇒ Object
readonly
Returns the value of attribute operation_name.
-
#operations ⇒ Object
readonly
Returns the value of attribute operations.
-
#origin ⇒ Object
(also: #controller, #channel)
readonly
Returns the value of attribute origin.
-
#response ⇒ Object
readonly
Returns the value of attribute response.
-
#schema ⇒ Object
readonly
Returns the value of attribute schema.
-
#stack ⇒ Object
readonly
Returns the value of attribute stack.
-
#strategy ⇒ Object
readonly
Returns the value of attribute strategy.
-
#subscriptions ⇒ Object
readonly
Returns the value of attribute subscriptions.
Class Method Summary collapse
-
.compile(*args, schema: nil, namespace: :base, **xargs) ⇒ Object
Shortcut for initialize and compile.
-
.const_defined?(name) ⇒ Boolean
Allow accessing component-based objects through the request.
-
.const_missing(name) ⇒ Object
Allow accessing component-based objects through the request.
-
.execute(*args, schema: nil, namespace: :base, context: {}, **xargs) ⇒ Object
Shortcut for initialize, set context, and execute.
-
.valid?(*args, schema: nil, namespace: :base, **xargs) ⇒ Boolean
Shortcut for initialize and validate.
Instance Method Summary collapse
-
#build(klass, *args, &block) ⇒ Object
This initiates a new object which is aware of class extensions.
-
#build_from_cache(klass) ⇒ Object
This allocates a new object which is aware of class extensions.
-
#build_rescue_object(**extra) ⇒ Object
Build a easy-to-access object representing the current information of the execution to be used on
rescue_with_handler. -
#cache(key, init_value = nil, &block) ⇒ Object
A shared way to cache information across the execution of an request.
-
#cache_dump ⇒ Object
Build the object that represent the request in the cache format.
-
#cache_load(data) ⇒ Object
Read the request from the cache to run it faster.
-
#compile(document, compress: true) ⇒ Object
Compile a given document.
-
#context ⇒ Object
Get the context of the request.
-
#context=(data) ⇒ Object
Set the context of the request, it must be a
Hash. -
#exception_to_error(exception, node, **xargs) ⇒ Object
Add the given
exceptionto the errors using thenodelocation. -
#execute(document, **xargs) ⇒ Object
(also: #perform)
Execute a given document with the given arguments.
-
#extend(*modules) ⇒ Object
Add class extensions to the request, which ensures a bunch of extended behaviors for all the objects created through the request.
-
#extensions ⇒ Object
Allow adding extra information to the response, in a extensions key.
-
#force_response(response, error = StaticResponse) ⇒ Object
This is used by cache and static responses to jump from executing to delivery a response right away.
-
#import_prepared_data(prepared_data) ⇒ Object
Import prepared data that is formatted as a hash.
-
#initialize(schema = nil, namespace: :base) ⇒ Request
constructor
Forces the schema to be registered on type map before moving forward.
-
#location_of(node) ⇒ Object
Get the location object of a given node.
-
#nested_cache(key, sub_key) ⇒ Object
A better way to ensure that nil values in a hash cache won’t be reinitialized.
-
#prepare_data_for(field, value, **options) ⇒ Object
Add a new prepared data from
valueto the givenfield. -
#prepared_data_for(field) ⇒ Object
Recover the next prepared data for the given field.
-
#prepared_data_for?(field) ⇒ Boolean
Check if the given field has prepared data.
-
#read_cache_request(data = @document) ⇒ Object
Read the request from the cache to run it faster.
-
#report_error(message, **xargs) ⇒ Object
The final helper that facilitates how errors are reported.
-
#report_node_error(message, node, **xargs) ⇒ Object
A little helper to report an error on a given node.
-
#rescue_with_handler(exception, **extra) ⇒ Object
Use schema handlers for exceptions caught during the execution process.
-
#stack_to_path ⇒ Object
Convert the current stack into a error path ignoring the schema.
-
#stacked(object, &block) ⇒ Object
Add the given
objectinto the executionstackand execute the givenblockmaking sure to rescue exceptions using therescue_with_handler. -
#subscriptions? ⇒ Boolean
Check if any new subscription was added.
-
#valid?(document) ⇒ Boolean
Check if the given document is valid by piggybacking on the compile process.
-
#valid_cache? ⇒ Boolean
Show if the current cached operation is still valid.
-
#write_cache_request(hash, data = cache_dump) ⇒ Object
Write the request into the cache so it can run again faster.
Constructor Details
#initialize(schema = nil, namespace: :base) ⇒ Request
Forces the schema to be registered on type map before moving forward
108 109 110 111 112 113 |
# File 'lib/rails/graphql/request.rb', line 108 def initialize(schema = nil, namespace: :base) @namespace = schema&.namespace || namespace @schema = GraphQL::Schema.find!(@namespace) ensure_schema! end |
Instance Attribute Details
#args ⇒ Object (readonly) Also known as: arguments
Returns the value of attribute args.
65 66 67 |
# File 'lib/rails/graphql/request.rb', line 65 def args @args end |
#document ⇒ Object (readonly)
Returns the value of attribute document.
65 66 67 |
# File 'lib/rails/graphql/request.rb', line 65 def document @document end |
#errors ⇒ Object (readonly)
Returns the value of attribute errors.
65 66 67 |
# File 'lib/rails/graphql/request.rb', line 65 def errors @errors end |
#fragments ⇒ Object (readonly)
Returns the value of attribute fragments.
65 66 67 |
# File 'lib/rails/graphql/request.rb', line 65 def fragments @fragments end |
#operation_name ⇒ Object (readonly)
Returns the value of attribute operation_name.
65 66 67 |
# File 'lib/rails/graphql/request.rb', line 65 def operation_name @operation_name end |
#operations ⇒ Object (readonly)
Returns the value of attribute operations.
65 66 67 |
# File 'lib/rails/graphql/request.rb', line 65 def operations @operations end |
#origin ⇒ Object (readonly) Also known as: controller, channel
Returns the value of attribute origin.
65 66 67 |
# File 'lib/rails/graphql/request.rb', line 65 def origin @origin end |
#response ⇒ Object (readonly)
Returns the value of attribute response.
65 66 67 |
# File 'lib/rails/graphql/request.rb', line 65 def response @response end |
#schema ⇒ Object (readonly)
Returns the value of attribute schema.
65 66 67 |
# File 'lib/rails/graphql/request.rb', line 65 def schema @schema end |
#stack ⇒ Object (readonly)
Returns the value of attribute stack.
65 66 67 |
# File 'lib/rails/graphql/request.rb', line 65 def stack @stack end |
#strategy ⇒ Object (readonly)
Returns the value of attribute strategy.
65 66 67 |
# File 'lib/rails/graphql/request.rb', line 65 def strategy @strategy end |
#subscriptions ⇒ Object (readonly)
Returns the value of attribute subscriptions.
65 66 67 |
# File 'lib/rails/graphql/request.rb', line 65 def subscriptions @subscriptions end |
Class Method Details
.compile(*args, schema: nil, namespace: :base, **xargs) ⇒ Object
Shortcut for initialize and compile
87 88 89 |
# File 'lib/rails/graphql/request.rb', line 87 def compile(*args, schema: nil, namespace: :base, **xargs) new(schema, namespace: namespace).compile(*args, **xargs) end |
.const_defined?(name) ⇒ Boolean
Allow accessing component-based objects through the request
97 98 99 |
# File 'lib/rails/graphql/request.rb', line 97 def const_defined?(name, *) Component.const_defined?(name, false) || super end |
.const_missing(name) ⇒ Object
Allow accessing component-based objects through the request
102 103 104 |
# File 'lib/rails/graphql/request.rb', line 102 def const_missing(name) Component.const_defined?(name, false) ? Component.const_get(name, false) : super end |
.execute(*args, schema: nil, namespace: :base, context: {}, **xargs) ⇒ Object
Shortcut for initialize, set context, and execute
80 81 82 83 84 |
# File 'lib/rails/graphql/request.rb', line 80 def execute(*args, schema: nil, namespace: :base, context: {}, **xargs) result = new(schema, namespace: namespace) result.context = context if context.present? result.execute(*args, **xargs) end |
.valid?(*args, schema: nil, namespace: :base, **xargs) ⇒ Boolean
Shortcut for initialize and validate
92 93 94 |
# File 'lib/rails/graphql/request.rb', line 92 def valid?(*args, schema: nil, namespace: :base, **xargs) new(schema, namespace: namespace).valid?(*args, **xargs) end |
Instance Method Details
#build(klass, *args, &block) ⇒ Object
This initiates a new object which is aware of class extensions
301 302 303 304 305 306 |
# File 'lib/rails/graphql/request.rb', line 301 def build(klass, *args, &block) ext_module = class_extensions[klass] obj = klass.new(*args, &block) obj.extend(ext_module) if ext_module obj end |
#build_from_cache(klass) ⇒ Object
This allocates a new object which is aware of class extensions
309 310 311 312 313 314 |
# File 'lib/rails/graphql/request.rb', line 309 def build_from_cache(klass) ext_module = class_extensions[klass] obj = klass.allocate obj.extend(ext_module) if ext_module obj end |
#build_rescue_object(**extra) ⇒ Object
Build a easy-to-access object representing the current information of the execution to be used on rescue_with_handler
229 230 231 232 233 234 235 236 237 |
# File 'lib/rails/graphql/request.rb', line 229 def build_rescue_object(**extra) OpenStruct.new(extra.reverse_merge( args: @args, source: stack.first, request: self, response: @response, document: @document, )).freeze end |
#cache(key, init_value = nil, &block) ⇒ Object
A shared way to cache information across the execution of an request
317 318 319 |
# File 'lib/rails/graphql/request.rb', line 317 def cache(key, init_value = nil, &block) @cache[key] ||= (init_value || block&.call || {}) end |
#cache_dump ⇒ Object
Build the object that represent the request in the cache format
350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/rails/graphql/request.rb', line 350 def cache_dump { strategy: @strategy.cache_dump, operation_name: @operation_name, type_map_version: schema.version, document: @document, errors: @errors.cache_dump, operations: @operations.transform_values(&:cache_dump), fragments: @fragments&.transform_values { |f| f.try(:cache_dump) }&.compact, } end |
#cache_load(data) ⇒ Object
Read the request from the cache to run it faster
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 |
# File 'lib/rails/graphql/request.rb', line 363 def cache_load(data) version = data[:type_map_version] @document = data[:document] @operation_name = data[:operation_name] resolve_from_cache = (version == schema.version) # Run the document from scratch if TypeMap has changed # TODO: We need to save the new organized document return run_document unless resolve_from_cache @valid_cache = true unless defined?(@valid_cache) # Run the document as a cached operation errors.cache_load(data[:errors]) @strategy = build(data[:strategy][:class], self) @strategy.trigger_event(:request) @strategy.cache_load(data) @strategy.resolve! end |
#compile(document, compress: true) ⇒ Object
Compile a given document
158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/rails/graphql/request.rb', line 158 def compile(document, compress: true) reset! log_execution(document, event: 'compile.graphql') do @document = initialize_document(document) run_document(with: :compile) result = Marshal.dump(cache_dump) result = Zlib.deflate(result) if compress @log_extra[:total] = result.bytesize result end end |
#context ⇒ Object
Get the context of the request
121 122 123 |
# File 'lib/rails/graphql/request.rb', line 121 def context @context ||= OpenStruct.new.freeze end |
#context=(data) ⇒ Object
Set the context of the request, it must be a Hash
126 127 128 |
# File 'lib/rails/graphql/request.rb', line 126 def context=(data) @context = build_ostruct(data).freeze end |
#exception_to_error(exception, node, **xargs) ⇒ Object
Add the given exception to the errors using the node location
246 247 248 249 |
# File 'lib/rails/graphql/request.rb', line 246 def exception_to_error(exception, node, **xargs) xargs[:exception] = exception.class.name report_node_error(xargs.delete(:message) || exception., node, **xargs) end |
#execute(document, **xargs) ⇒ Object Also known as: perform
Execute a given document with the given arguments
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/rails/graphql/request.rb', line 136 def execute(document, **xargs) output = xargs.delete(:as) || schema.config.default_response_format cache = xargs.delete(:hash) formatter = RESPONSE_FORMATS[output] document, cache = nil, document if xargs.delete(:compiled) prepared_data = xargs.delete(:data_for) reset!(**xargs) @response = initialize_response(output, formatter) import_prepared_data(prepared_data) execute!(document, cache) response.public_send(formatter) rescue StaticResponse # TODO: Maybe change this to a throw/catch instead response.public_send(formatter) end |
#extend(*modules) ⇒ Object
Add class extensions to the request, which ensures a bunch of extended behaviors for all the objects created through the request
294 295 296 297 298 |
# File 'lib/rails/graphql/request.rb', line 294 def extend(*modules) import_class_extensions(*modules) request_ext = class_extensions[self.class] super(request_ext) if request_ext && !is_a?(request_ext) end |
#extensions ⇒ Object
Allow adding extra information to the response, in a extensions key
131 132 133 |
# File 'lib/rails/graphql/request.rb', line 131 def extensions @extensions ||= {} end |
#force_response(response, error = StaticResponse) ⇒ Object
This is used by cache and static responses to jump from executing to delivery a response right away
187 188 189 190 191 |
# File 'lib/rails/graphql/request.rb', line 187 def force_response(response, error = StaticResponse) return unless defined?(@response) @response = response raise error end |
#import_prepared_data(prepared_data) ⇒ Object
Import prepared data that is formatted as a hash
194 195 196 197 198 |
# File 'lib/rails/graphql/request.rb', line 194 def import_prepared_data(prepared_data) prepared_data&.each do |key, value| prepare_data_for(key, value) end end |
#location_of(node) ⇒ Object
Get the location object of a given node
258 259 260 261 262 263 264 265 266 |
# File 'lib/rails/graphql/request.rb', line 258 def location_of(node) node = node.instance_variable_get(:@node) if node.is_a?(Request::Component) return unless node.is_a?(GQLParser::Token) [ { 'line' => node.begin_line, 'column' => node.begin_column }, { 'line' => node.end_line, 'column' => node.end_column }, ] end |
#nested_cache(key, sub_key) ⇒ Object
A better way to ensure that nil values in a hash cache won’t be reinitialized
323 324 325 |
# File 'lib/rails/graphql/request.rb', line 323 def nested_cache(key, sub_key) (source = cache(key)).key?(sub_key) ? source[sub_key] : source[sub_key] = yield end |
#prepare_data_for(field, value, **options) ⇒ Object
Add a new prepared data from value to the given field
201 202 203 204 205 206 207 208 209 |
# File 'lib/rails/graphql/request.rb', line 201 def prepare_data_for(field, value, **) field = PreparedData.lookup(self, field) if prepared_data.key?(field) prepared_data[field].push(value) else prepared_data[field] = PreparedData.new(field, value, **) end end |
#prepared_data_for(field) ⇒ Object
Recover the next prepared data for the given field
212 213 214 215 216 217 |
# File 'lib/rails/graphql/request.rb', line 212 def prepared_data_for(field) return unless defined?(@prepared_data) field = field.field if field.is_a?(Component::Field) prepared_data[field] end |
#prepared_data_for?(field) ⇒ Boolean
Check if the given field has prepared data
220 221 222 223 224 225 |
# File 'lib/rails/graphql/request.rb', line 220 def prepared_data_for?(field) return false unless defined?(@prepared_data) field = field.field if field.is_a?(Component::Field) prepared_data.key?(field) end |
#read_cache_request(data = @document) ⇒ Object
Read the request from the cache to run it faster
338 339 340 341 342 343 344 345 346 347 |
# File 'lib/rails/graphql/request.rb', line 338 def read_cache_request(data = @document) begin data = Zlib.inflate(data) if data[0] == 'x' data = Marshal.load(data) rescue Zlib::BufError, ArgumentError raise ::ArgumentError, +'Unable to recover the cached request.' end cache_load(data) end |
#report_error(message, **xargs) ⇒ Object
The final helper that facilitates how errors are reported
269 270 271 272 273 274 |
# File 'lib/rails/graphql/request.rb', line 269 def report_error(, **xargs) xargs[:path] ||= stack_to_path errors.add(, **xargs) nil # Return nil for easier usage end |
#report_node_error(message, node, **xargs) ⇒ Object
A little helper to report an error on a given node
252 253 254 255 |
# File 'lib/rails/graphql/request.rb', line 252 def report_node_error(, node, **xargs) xargs[:locations] ||= location_of(node) report_error(, **xargs) end |
#rescue_with_handler(exception, **extra) ⇒ Object
Use schema handlers for exceptions caught during the execution process
240 241 242 243 |
# File 'lib/rails/graphql/request.rb', line 240 def rescue_with_handler(exception, **extra) ExtendedError.extend(exception, build_rescue_object(**extra)) schema.rescue_with_handler(exception) end |
#stack_to_path ⇒ Object
Convert the current stack into a error path ignoring the schema
286 287 288 289 290 |
# File 'lib/rails/graphql/request.rb', line 286 def stack_to_path stack[0..-2].map do |item| item.is_a?(Numeric) ? item : item.try(:gql_name) end.compact.reverse end |
#stacked(object, &block) ⇒ Object
Add the given object into the execution stack and execute the given block making sure to rescue exceptions using the rescue_with_handler
278 279 280 281 282 283 |
# File 'lib/rails/graphql/request.rb', line 278 def stacked(object, &block) stack.unshift(object) block.call ensure stack.shift end |
#subscriptions? ⇒ Boolean
Check if any new subscription was added
116 117 118 |
# File 'lib/rails/graphql/request.rb', line 116 def subscriptions? defined?(@subscriptions) && @subscriptions.any? end |
#valid?(document) ⇒ Boolean
Check if the given document is valid by piggybacking on the compile process
175 176 177 178 179 180 181 182 183 |
# File 'lib/rails/graphql/request.rb', line 175 def valid?(document) reset! log_execution(document, event: 'validate.graphql') do @document = initialize_document(document) run_document(with: :compile) @log_extra[:result] = @errors.empty? end end |
#valid_cache? ⇒ Boolean
Show if the current cached operation is still valid
328 329 330 |
# File 'lib/rails/graphql/request.rb', line 328 def valid_cache? defined?(@valid_cache) && @valid_cache end |
#write_cache_request(hash, data = cache_dump) ⇒ Object
Write the request into the cache so it can run again faster
333 334 335 |
# File 'lib/rails/graphql/request.rb', line 333 def write_cache_request(hash, data = cache_dump) schema.write_on_cache(hash, Marshal.dump(data)) end |