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



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/graphql/client/query_result.rb', line 257

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

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

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

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

Class Attribute Details

.fieldsObject (readonly)

Returns the value of attribute fields.



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

def fields
  @fields
end

.source_definitionObject (readonly)

Returns the value of attribute source_definition.



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

def source_definition
  @source_definition
end

.source_nodeObject (readonly)

Returns the value of attribute source_node.



156
157
158
# File 'lib/graphql/client/query_result.rb', line 156

def source_node
  @source_node
end

.typeObject (readonly)

Returns the value of attribute type.



152
153
154
# File 'lib/graphql/client/query_result.rb', line 152

def type
  @type
end

Instance Attribute Details

#__typenameObject (readonly) Also known as: typename

Returns the value of attribute __typename.



239
240
241
# File 'lib/graphql/client/query_result.rb', line 239

def __typename
  @__typename
end

#dataObject (readonly) Also known as: to_h

Returns the value of attribute data.



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

def data
  @data
end

#errorsObject (readonly)

Public: Return errors associated with data.

Returns Errors collection.



234
235
236
# File 'lib/graphql/client/query_result.rb', line 234

def errors
  @errors
end

Class Method Details

.[](name) ⇒ Object



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

def [](name)
  fields[name]
end

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



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/graphql/client/query_result.rb', line 173

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, "couldn't cast #{obj.inspect} to #{inspect}"
    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
140
141
142
143
144
145
146
147
148
149
# 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.fields[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\n        end\n      RUBY\n      field_readers << \"\#{field_alias}?\".to_sym\n\n      next unless field == \"edges\"\n      class_eval <<-RUBY, __FILE__, __LINE__\n        def each_node\n          return enum_for(:each_node) unless block_given?\n          edges.each { |edge| yield edge.node }\n          self\n        end\n      RUBY\n      field_readers << :each_node\n    end\n\n    assigns = @fields.map do |field, klass|\n      if klass\n        <<-RUBY\n          @\#{field} = self.class.fields[:\#{field}].cast(@data[\"\#{field}\"], @errors.filter_by_path(\"\#{field}\"))\n        RUBY\n      else\n        <<-RUBY\n          @\#{field} = @data[\"\#{field}\"]\n        RUBY\n      end\n    end\n\n    if @type && @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



169
170
171
# File 'lib/graphql/client/query_result.rb', line 169

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

.nameObject



165
166
167
# File 'lib/graphql/client/query_result.rb', line 165

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

.new(obj, *args) ⇒ Object



209
210
211
212
213
214
215
216
# File 'lib/graphql/client/query_result.rb', line 209

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

.spreads(node) ⇒ Object

Internal



196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/graphql/client/query_result.rb', line 196

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



218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/graphql/client/query_result.rb', line 218

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



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

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)


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

def respond_to_missing?(*args)
  super
end