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



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/graphql/client/query_result.rb', line 253

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.



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

def fields
  @fields
end

.source_definitionObject (readonly)

Returns the value of attribute source_definition.



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

def source_definition
  @source_definition
end

.source_nodeObject (readonly)

Returns the value of attribute source_node.



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

def source_node
  @source_node
end

.typeObject (readonly)

Returns the value of attribute type.



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

def type
  @type
end

Instance Attribute Details

#__typenameObject (readonly) Also known as: typename

Returns the value of attribute __typename.



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

def __typename
  @__typename
end

#dataObject (readonly) Also known as: to_h

Returns the value of attribute data.



232
233
234
# File 'lib/graphql/client/query_result.rb', line 232

def data
  @data
end

#errorsObject (readonly)

Public: Return errors associated with data.

Returns Errors collection.



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

def errors
  @errors
end

Class Method Details

.[](name) ⇒ Object



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

def [](name)
  fields[name]
end

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



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

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



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

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 <<-RUBY, __FILE__, __LINE__
        def #{field_alias}?
          #{field_alias} ? true : false
        end
      RUBY
      field_readers << "#{field_alias}?".to_sym

      next unless field == "edges"
      class_eval <<-RUBY, __FILE__, __LINE__
        def each_node
          return enum_for(:each_node) unless block_given?
          edges.each { |edge| yield edge.node }
          self
        end
      RUBY
      field_readers << :each_node
    end

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

    if @type && @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



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

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

.nameObject



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

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

.new(obj, *args) ⇒ Object



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

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

.spreads(node) ⇒ Object

Internal



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

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



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

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



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

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)


276
277
278
# File 'lib/graphql/client/query_result.rb', line 276

def respond_to_missing?(*args)
  super
end