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



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/graphql/client/query_result.rb', line 379

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.



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

def fields
  @fields
end

.source_definitionObject (readonly)

Returns the value of attribute source_definition.



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

def source_definition
  @source_definition
end

.source_nodeObject (readonly)

Returns the value of attribute source_node.



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

def source_node
  @source_node
end

.typeObject (readonly)

Returns the value of attribute type.



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

def type
  @type
end

Instance Attribute Details

#__typenameObject (readonly) Also known as: typename

Returns the value of attribute __typename.



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

def __typename
  @__typename
end

#errorsObject (readonly)

Public: Return errors associated with data.

Returns Errors collection.



342
343
344
# File 'lib/graphql/client/query_result.rb', line 342

def errors
  @errors
end

Class Method Details

.[](name) ⇒ Object



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

def [](name)
  fields[name]
end

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



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/graphql/client/query_result.rb', line 283

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



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
251
252
253
254
255
# File 'lib/graphql/client/query_result.rb', line 202

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 <<-RUBY, __FILE__, __LINE__
        def #{field_alias}?
          #{field_alias} ? true : false
        end
      RUBY
      field_readers << "#{field_alias}?".to_sym
    end

    assigns = @fields.map do |field, klass|
      <<-RUBY
        @#{field} = self.class.fields[:#{field}].cast(@data["#{field}"], @errors.filter_by_path("#{field}"))
      RUBY
    end

    if @type.is_a?(GraphQL::ObjectType)
      assigns.unshift "@__typename = self.class.type.name"
    end

    class_eval <<-RUBY, __FILE__, __LINE__
      def initialize(data, errors = Errors.new)
        @data = data
        @errors = errors

        #{assigns.join("\n")}
        freeze
      end
    RUBY

    if @source_definition.enforce_collocated_callers
      Client.enforce_collocated_callers(self, field_readers, source_definition.source_location[0])
    end
  end
end

.inspectObject



279
280
281
# File 'lib/graphql/client/query_result.rb', line 279

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

.nameObject



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

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

.new(obj, *args) ⇒ Object



317
318
319
320
321
322
323
324
# File 'lib/graphql/client/query_result.rb', line 317

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

.schemaObject



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

def schema
  source_definition.schema
end

.spreads(node) ⇒ Object

Internal



304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/graphql/client/query_result.rb', line 304

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
78
# 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]
  #         p [:merge, 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, GraphQL::UnionType
    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



326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/graphql/client/query_result.rb', line 326

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



364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/graphql/client/query_result.rb', line 364

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)


406
407
408
# File 'lib/graphql/client/query_result.rb', line 406

def respond_to_missing?(*args)
  super
end

#to_hObject

Public: Returns the raw response data

Returns Hash



350
351
352
# File 'lib/graphql/client/query_result.rb', line 350

def to_h
  @data
end

#type_of?(*types) ⇒ Boolean

Returns:

  • (Boolean)


354
355
356
357
358
359
360
361
362
# File 'lib/graphql/client/query_result.rb', line 354

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