Class: GraphQL::Client::QueryResult

Inherits:
Object
  • Object
show all
Defined in:
lib/graphql/client/query_result.rb

Overview

A QueryResult struct wraps data returned from a GraphQL response.

Wrapping the JSON-like Hash allows access with nice Ruby accessor methods rather than using ‘obj` access.

Wrappers also limit field visibility to fragment definitions.

Defined Under Namespace

Classes: EnumWrapper, ImplicitlyFetchedFieldError, ListWrapper, NoFieldError, NonNullWrapper, ScalarWrapper, UnfetchedFieldError, UnimplementedFieldError, UnionWrapper

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(*args) ⇒ Object



374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/graphql/client/query_result.rb', line 374

def method_missing(*args)
  super
rescue NoMethodError => e
  type = self.class.type
  raise e unless type

  field = type.all_fields.find do |f|
    f.name == e.name.to_s || ActiveSupport::Inflector.underscore(f.name) == e.name.to_s
  end

  unless field
    raise UnimplementedFieldError, "undefined field `#{e.name}' on #{type} type. https://git.io/v1y3m"
  end

  if @data[field.name]
    error_class = ImplicitlyFetchedFieldError
    message = "implicitly fetched field `#{field.name}' on #{type} type. https://git.io/v1yGL"
  else
    error_class = UnfetchedFieldError
    message = "unfetched field `#{field.name}' on #{type} type. https://git.io/v1y3U"
  end

  node = self.class.source_node
  message += "\n\n" + node.to_query_string.sub(/\}$/, "+ #{field.name}\n}") if node
  raise error_class, message
end

Class Attribute Details

.fieldsObject (readonly)

Returns the value of attribute fields.



259
260
261
# File 'lib/graphql/client/query_result.rb', line 259

def fields
  @fields
end

.source_definitionObject (readonly)

Returns the value of attribute source_definition.



255
256
257
# File 'lib/graphql/client/query_result.rb', line 255

def source_definition
  @source_definition
end

.source_nodeObject (readonly)

Returns the value of attribute source_node.



257
258
259
# File 'lib/graphql/client/query_result.rb', line 257

def source_node
  @source_node
end

.typeObject (readonly)

Returns the value of attribute type.



253
254
255
# File 'lib/graphql/client/query_result.rb', line 253

def type
  @type
end

Instance Attribute Details

#__typenameObject (readonly) Also known as: typename

Returns the value of attribute __typename.



339
340
341
# File 'lib/graphql/client/query_result.rb', line 339

def __typename
  @__typename
end

#errorsObject (readonly)

Public: Return errors associated with data.

Returns Errors collection.



337
338
339
# File 'lib/graphql/client/query_result.rb', line 337

def errors
  @errors
end

Class Method Details

.[](name) ⇒ Object



265
266
267
# File 'lib/graphql/client/query_result.rb', line 265

def [](name)
  fields[name]
end

.cast(obj, errors = Errors.new) ⇒ Object



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/graphql/client/query_result.rb', line 278

def self.cast(obj, errors = Errors.new)
  case obj
  when Hash
    new(obj, errors)
  when self
    obj
  when QueryResult
    spreads = Set.new(self.spreads(obj.class.source_node).map(&:name))

    unless spreads.include?(source_node.name)
      raise TypeError, "#{self.source_definition.name} is not included in #{obj.class.source_definition.name}"
    end
    cast(obj.to_h, obj.errors)
  when NilClass
    nil
  else
    raise TypeError, "expected #{obj.inspect} to be a Hash"
  end
end

.define(name:, type:, source_definition:, source_node:, fields: {}) ⇒ Object

Internal



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/graphql/client/query_result.rb', line 197

def self.define(name:, type:, source_definition:, source_node:, fields: {})
  Class.new(self) do
    @name = name
    @type = type
    @source_node = source_node
    @source_definition = source_definition
    @fields = {}

    field_readers = Set.new

    fields.each do |field, klass|
      @fields[field.to_sym] = klass

      send :attr_reader, field
      field_readers << field.to_sym

      # Convert GraphQL camelcase to snake case: commitComments -> commit_comments
      field_alias = ActiveSupport::Inflector.underscore(field)
      send :alias_method, field_alias, field if field != field_alias
      field_readers << field_alias.to_sym

      class_eval "        def \#{field_alias}?\n          \#{field_alias} ? true : false\n        end\n      RUBY\n      field_readers << \"\#{field_alias}?\".to_sym\n    end\n\n    assigns = @fields.map do |field, klass|\n      <<-RUBY\n        @\#{field} = self.class.fields[:\#{field}].cast(@data[\"\#{field}\"], @errors.filter_by_path(\"\#{field}\"))\n      RUBY\n    end\n\n    if @type.is_a?(GraphQL::ObjectType)\n      assigns.unshift \"@__typename = self.class.type.name\"\n    end\n\n    class_eval <<-RUBY, __FILE__, __LINE__\n      def initialize(data, errors = Errors.new)\n        @data = data\n        @errors = errors\n\n        \#{assigns.join(\"\\n\")}\n        freeze\n      end\n    RUBY\n\n    if @source_definition.enforce_collocated_callers\n      Client.enforce_collocated_callers(self, field_readers, source_definition.source_location[0])\n    end\n  end\nend\n", __FILE__, __LINE__

.inspectObject



274
275
276
# File 'lib/graphql/client/query_result.rb', line 274

def self.inspect
  "#<#{name} fields=#{@fields.keys.inspect}>"
end

.nameObject



270
271
272
# File 'lib/graphql/client/query_result.rb', line 270

def self.name
  @name || super || GraphQL::Client::QueryResult.name
end

.new(obj, *args) ⇒ Object



312
313
314
315
316
317
318
319
# File 'lib/graphql/client/query_result.rb', line 312

def self.new(obj, *args)
  case obj
  when Hash
    super
  else
    cast(obj, *args)
  end
end

.schemaObject



261
262
263
# File 'lib/graphql/client/query_result.rb', line 261

def schema
  source_definition.schema
end

.spreads(node) ⇒ Object

Internal



299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/graphql/client/query_result.rb', line 299

def self.spreads(node)
  node.selections.flat_map do |selection|
    case selection
    when Language::Nodes::FragmentSpread
      selection
    when Language::Nodes::InlineFragment
      spreads(selection)
    else
      []
    end
  end
end

.wrap(source_definition, node, type, name: nil) ⇒ Object

Internal: Get QueryResult class for result of query.

Returns subclass of QueryResult or nil.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/graphql/client/query_result.rb', line 25

def self.wrap(source_definition, node, type, name: nil)
  case type
  when GraphQL::NonNullType
    NonNullWrapper.new(wrap(source_definition, node, type.of_type, name: name))
  when GraphQL::ListType
    ListWrapper.new(wrap(source_definition, node, type.of_type, name: name))
  when GraphQL::ScalarType
    ScalarWrapper.new(type)
  when GraphQL::EnumType
    EnumWrapper.new(type)
  when GraphQL::UnionType
    types = {}

    node.selections.each do |selection|
      case selection
      when Language::Nodes::InlineFragment
        selection_type = source_definition.document_types[selection]
        selection_wrapper = wrap(source_definition, selection, selection_type, name: name)
        if types[selection_type]
          types[selection_type.name] |= selection_wrapper
        else
          types[selection_type.name] = selection_wrapper
        end
      end
    end

    UnionWrapper.new(types)
  when GraphQL::ObjectType, GraphQL::InterfaceType
    fields = {}

    node.selections.each do |selection|
      case selection
      when Language::Nodes::FragmentSpread
        nil
      when Language::Nodes::Field
        field_name = selection.alias || selection.name
        selection_type = source_definition.document_types[selection]
        selection_type = GraphQL::STRING_TYPE if field_name == "__typename"
        field_klass = wrap(source_definition, selection, selection_type, name: "#{name}[:#{field_name}]")
        fields[field_name] ? fields[field_name] |= field_klass : fields[field_name] = field_klass
      when Language::Nodes::InlineFragment
        selection_type = source_definition.document_types[selection]
        wrap(source_definition, selection, selection_type, name: name).fields.each do |fragment_name, klass|
          fields[fragment_name.to_s] ? fields[fragment_name.to_s] |= klass : fields[fragment_name.to_s] = klass
        end
      end
    end

    define(name: name, type: type, source_definition: source_definition, source_node: node, fields: fields)
  else
    raise TypeError, "unexpected #{type.class}"
  end
end

.|(other) ⇒ Object



321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/graphql/client/query_result.rb', line 321

def self.|(other)
  new_fields = fields.dup
  other.fields.each do |name, value|
    if new_fields[name]
      new_fields[name] |= value
    else
      new_fields[name] = value
    end
  end
  # TODO: Picking first source node seems error prone
  define(name: self.name, type: self.type, source_definition: source_definition, source_node: source_node, fields: new_fields)
end

Instance Method Details

#inspectObject



359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/graphql/client/query_result.rb', line 359

def inspect
  ivars = self.class.fields.keys.map do |sym|
    value = instance_variable_get("@#{sym}")
    if value.is_a?(QueryResult)
      "#{sym}=#<#{value.class.name}>"
    else
      "#{sym}=#{value.inspect}"
    end
  end
  buf = "#<#{self.class.name}".dup
  buf << " " << ivars.join(" ") if ivars.any?
  buf << ">"
  buf
end

#respond_to_missing?(*args) ⇒ Boolean

Returns:

  • (Boolean)


401
402
403
# File 'lib/graphql/client/query_result.rb', line 401

def respond_to_missing?(*args)
  super
end

#to_hObject

Public: Returns the raw response data

Returns Hash



345
346
347
# File 'lib/graphql/client/query_result.rb', line 345

def to_h
  @data
end

#type_of?(*types) ⇒ Boolean

Returns:

  • (Boolean)


349
350
351
352
353
354
355
356
357
# File 'lib/graphql/client/query_result.rb', line 349

def type_of?(*types)
  types.any? do |type|
    if type = self.class.schema.types.fetch(type.to_s, nil)
      self.class.schema.possible_types(type).any? { |t| @__typename == t.name }
    else
      false
    end
  end
end