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, NoFieldError, Scalar, 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



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/graphql/client/query_result.rb', line 265

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.



148
149
150
# File 'lib/graphql/client/query_result.rb', line 148

def fields
  @fields
end

.source_definitionObject (readonly)

Returns the value of attribute source_definition.



144
145
146
# File 'lib/graphql/client/query_result.rb', line 144

def source_definition
  @source_definition
end

.source_nodeObject (readonly)

Returns the value of attribute source_node.



146
147
148
# File 'lib/graphql/client/query_result.rb', line 146

def source_node
  @source_node
end

.typeObject (readonly)

Returns the value of attribute type.



142
143
144
# File 'lib/graphql/client/query_result.rb', line 142

def type
  @type
end

Instance Attribute Details

#__typenameObject (readonly) Also known as: typename

Returns the value of attribute __typename.



230
231
232
# File 'lib/graphql/client/query_result.rb', line 230

def __typename
  @__typename
end

#errorsObject (readonly)

Public: Return errors associated with data.

Returns Errors collection.



228
229
230
# File 'lib/graphql/client/query_result.rb', line 228

def errors
  @errors
end

Class Method Details

.[](name) ⇒ Object



154
155
156
# File 'lib/graphql/client/query_result.rb', line 154

def [](name)
  fields[name]
end

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



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/graphql/client/query_result.rb', line 167

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 Array
    List.new(obj.each_with_index.map { |e, idx| cast(e, errors.filter_by_path(idx)) }, errors)
  when NilClass
    nil
  else
    raise TypeError, obj.class.to_s
  end
end

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

Internal



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/graphql/client/query_result.rb', line 70

def self.define(name:, source_definition:, source_node:, fields: {})
  type = source_definition.document_types[source_node]
  type = type.unwrap if type

  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|
      if @type.is_a?(GraphQL::ObjectType)
        field_node = @type.get_field(field.to_s)
        if field_node && field_node.type.unwrap.is_a?(GraphQL::ScalarType)
          klass = Scalar.new(field_node.type.unwrap)
        end
      end

      @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\nend\n", __FILE__, __LINE__
      field_readers << "#{field_alias}?".to_sym
    end

    assigns = @fields.map do |field, klass|
      if klass
        "@\#{field} = self.class.fields[:\#{field}].cast(@data[\"\#{field}\"], @errors.filter_by_path(\"\#{field}\"))\n"
      else
        "@\#{field} = @data[\"\#{field}\"]\n"
      end
    end

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

    class_eval "def initialize(data, errors = Errors.new)\n@data = data\n@errors = errors\n\n\#{assigns.join(\"\\n\")}\nfreeze\nend\n", __FILE__, __LINE__

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

.inspectObject



163
164
165
# File 'lib/graphql/client/query_result.rb', line 163

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

.nameObject



159
160
161
# File 'lib/graphql/client/query_result.rb', line 159

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

.new(obj, *args) ⇒ Object



203
204
205
206
207
208
209
210
# File 'lib/graphql/client/query_result.rb', line 203

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

.schemaObject



150
151
152
# File 'lib/graphql/client/query_result.rb', line 150

def schema
  source_definition.schema
end

.spreads(node) ⇒ Object

Internal



190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/graphql/client/query_result.rb', line 190

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, 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
# File 'lib/graphql/client/query_result.rb', line 25

def self.wrap(source_definition, node, name: nil)
  fields = {}

  node.selections.each do |selection|
    case selection
    when Language::Nodes::FragmentSpread
      nil
    when Language::Nodes::Field
      field_name = selection.alias || selection.name
      field_klass = nil
      if selection.selections.any?
        field_klass = wrap(source_definition, selection, name: "#{name}[:#{field_name}]")
      end
      fields[field_name] ? fields[field_name] |= field_klass : fields[field_name] = field_klass
    when Language::Nodes::InlineFragment
      wrap(source_definition, selection, 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, source_definition: source_definition, source_node: node, fields: fields)
end

.|(other) ⇒ Object



212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/graphql/client/query_result.rb', line 212

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, source_definition: source_definition, source_node: source_node, fields: new_fields)
end

Instance Method Details

#inspectObject



250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/graphql/client/query_result.rb', line 250

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)


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

def respond_to_missing?(*args)
  super
end

#to_hObject

Public: Returns the raw response data

Returns Hash



236
237
238
# File 'lib/graphql/client/query_result.rb', line 236

def to_h
  @data
end

#type_of?(*types) ⇒ Boolean

Returns:

  • (Boolean)


240
241
242
243
244
245
246
247
248
# File 'lib/graphql/client/query_result.rb', line 240

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