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: ImplicitlyFetchedFieldError, ListWrapper, NoFieldError, NonNullWrapper, ScalarWrapper, UnfetchedFieldError, UnimplementedFieldError

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



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/graphql/client/query_result.rb', line 320

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.



205
206
207
# File 'lib/graphql/client/query_result.rb', line 205

def fields
  @fields
end

.source_definitionObject (readonly)

Returns the value of attribute source_definition.



201
202
203
# File 'lib/graphql/client/query_result.rb', line 201

def source_definition
  @source_definition
end

.source_nodeObject (readonly)

Returns the value of attribute source_node.



203
204
205
# File 'lib/graphql/client/query_result.rb', line 203

def source_node
  @source_node
end

.typeObject (readonly)

Returns the value of attribute type.



199
200
201
# File 'lib/graphql/client/query_result.rb', line 199

def type
  @type
end

Instance Attribute Details

#__typenameObject (readonly) Also known as: typename

Returns the value of attribute __typename.



285
286
287
# File 'lib/graphql/client/query_result.rb', line 285

def __typename
  @__typename
end

#errorsObject (readonly)

Public: Return errors associated with data.

Returns Errors collection.



283
284
285
# File 'lib/graphql/client/query_result.rb', line 283

def errors
  @errors
end

Class Method Details

.[](name) ⇒ Object



211
212
213
# File 'lib/graphql/client/query_result.rb', line 211

def [](name)
  fields[name]
end

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



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/graphql/client/query_result.rb', line 224

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



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/graphql/client/query_result.rb', line 143

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



220
221
222
# File 'lib/graphql/client/query_result.rb', line 220

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

.nameObject



216
217
218
# File 'lib/graphql/client/query_result.rb', line 216

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

.new(obj, *args) ⇒ Object



258
259
260
261
262
263
264
265
# File 'lib/graphql/client/query_result.rb', line 258

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

.schemaObject



207
208
209
# File 'lib/graphql/client/query_result.rb', line 207

def schema
  source_definition.schema
end

.spreads(node) ⇒ Object

Internal



245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/graphql/client/query_result.rb', line 245

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
# 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::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



267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/graphql/client/query_result.rb', line 267

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



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

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)


347
348
349
# File 'lib/graphql/client/query_result.rb', line 347

def respond_to_missing?(*args)
  super
end

#to_hObject

Public: Returns the raw response data

Returns Hash



291
292
293
# File 'lib/graphql/client/query_result.rb', line 291

def to_h
  @data
end

#type_of?(*types) ⇒ Boolean

Returns:

  • (Boolean)


295
296
297
298
299
300
301
302
303
# File 'lib/graphql/client/query_result.rb', line 295

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