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



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/graphql/client/query_result.rb', line 247

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.



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.



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

def __typename
  @__typename
end

#dataObject (readonly) Also known as: to_h

Returns the value of attribute data.



226
227
228
# File 'lib/graphql/client/query_result.rb', line 226

def data
  @data
end

#errorsObject (readonly)

Public: Return errors associated with data.

Returns Errors collection.



224
225
226
# File 'lib/graphql/client/query_result.rb', line 224

def errors
  @errors
end

Class Method Details

.[](name) ⇒ Object



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

def [](name)
  fields[name]
end

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



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/graphql/client/query_result.rb', line 163

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



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

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

.nameObject



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

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

.new(obj, *args) ⇒ Object



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

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

.spreads(node) ⇒ Object

Internal



186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/graphql/client/query_result.rb', line 186

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



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

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



232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/graphql/client/query_result.rb', line 232

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



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

def respond_to_missing?(*args)
  super
end