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, ListWrapper, NonNullWrapper, ScalarWrapper, 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



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

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

  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.



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

def __typename
  @__typename
end

#errorsObject (readonly)

Public: Return errors associated with data.

Returns Errors collection.



335
336
337
# File 'lib/graphql/client/query_result.rb', line 335

def errors
  @errors
end

Class Method Details

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



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

def self.cast(obj, errors = Errors.new)
  case obj
  when Hash
    new(obj, errors)
  when Array
    obj.map { |o| new(o, 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 <<-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



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

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

.nameObject



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

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

.new(obj, *args) ⇒ Object



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

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



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

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.



20
21
22
23
24
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
# File 'lib/graphql/client/query_result.rb', line 20

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



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

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



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

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)


397
398
399
# File 'lib/graphql/client/query_result.rb', line 397

def respond_to_missing?(*args)
  super
end

#to_hObject

Public: Returns the raw response data

Returns Hash



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

def to_h
  @data
end

#type_of?(*types) ⇒ Boolean

Returns:

  • (Boolean)


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

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