diff --git a/README.md b/README.md
index 4c1df7b..7a3cb92 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# graphql-client [![Gem Version](https://badge.fury.io/rb/graphql-client.svg)](https://badge.fury.io/rb/graphql-client) [![Build Status](https://travis-ci.org/github/graphql-client.svg?branch=master)](https://travis-ci.org/github/graphql-client)
+# graphql-client [![Gem Version](https://badge.fury.io/rb/graphql-client.svg)](https://badge.fury.io/rb/graphql-client) [![CI](https://github.com/github/graphql-client/workflows/CI/badge.svg)](https://github.com/github/graphql-client/actions?query=workflow)
 
 GraphQL Client is a Ruby library for declaring, composing and executing GraphQL queries.
 
diff --git a/graphql-client.gemspec b/graphql-client.gemspec
index 980820e..46c92d3 100644
--- a/graphql-client.gemspec
+++ b/graphql-client.gemspec
@@ -2,23 +2,23 @@
 # This file has been automatically generated by gem2tgz #
 #########################################################
 # -*- encoding: utf-8 -*-
-# stub: graphql-client 0.16.0 ruby lib
+# stub: graphql-client 0.18.0 ruby lib
 
 Gem::Specification.new do |s|
   s.name = "graphql-client".freeze
-  s.version = "0.16.0"
+  s.version = "0.18.0"
 
   s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
   s.require_paths = ["lib".freeze]
   s.authors = ["GitHub".freeze]
-  s.date = "2019-10-09"
+  s.date = "2022-05-02"
   s.description = "A Ruby library for declaring, composing and executing GraphQL queries".freeze
   s.email = "engineering@github.com".freeze
   s.files = ["LICENSE".freeze, "README.md".freeze, "lib/graphql/client.rb".freeze, "lib/graphql/client/collocated_enforcement.rb".freeze, "lib/graphql/client/definition.rb".freeze, "lib/graphql/client/definition_variables.rb".freeze, "lib/graphql/client/document_types.rb".freeze, "lib/graphql/client/erb.rb".freeze, "lib/graphql/client/error.rb".freeze, "lib/graphql/client/errors.rb".freeze, "lib/graphql/client/erubi_enhancer.rb".freeze, "lib/graphql/client/erubis.rb".freeze, "lib/graphql/client/erubis_enhancer.rb".freeze, "lib/graphql/client/fragment_definition.rb".freeze, "lib/graphql/client/hash_with_indifferent_access.rb".freeze, "lib/graphql/client/http.rb".freeze, "lib/graphql/client/list.rb".freeze, "lib/graphql/client/log_subscriber.rb".freeze, "lib/graphql/client/operation_definition.rb".freeze, "lib/graphql/client/query_typename.rb".freeze, "lib/graphql/client/railtie.rb".freeze, "lib/graphql/client/response.rb".freeze, "lib/graphql/client/schema.rb".freeze, "lib/graphql/client/schema/base_type.rb".freeze, "lib/graphql/client/schema/enum_type.rb".freeze, "lib/graphql/client/schema/include_directive.rb".freeze, "lib/graphql/client/schema/interface_type.rb".freeze, "lib/graphql/client/schema/list_type.rb".freeze, "lib/graphql/client/schema/non_null_type.rb".freeze, "lib/graphql/client/schema/object_type.rb".freeze, "lib/graphql/client/schema/possible_types.rb".freeze, "lib/graphql/client/schema/scalar_type.rb".freeze, "lib/graphql/client/schema/skip_directive.rb".freeze, "lib/graphql/client/schema/union_type.rb".freeze, "lib/graphql/client/view_module.rb".freeze, "lib/rubocop/cop/graphql/heredoc.rb".freeze, "lib/rubocop/cop/graphql/overfetch.rb".freeze]
   s.homepage = "https://github.com/github/graphql-client".freeze
   s.licenses = ["MIT".freeze]
   s.required_ruby_version = Gem::Requirement.new(">= 2.1.0".freeze)
-  s.rubygems_version = "3.2.0.rc.2".freeze
+  s.rubygems_version = "3.2.5".freeze
   s.summary = "GraphQL Client".freeze
 
   if s.respond_to? :specification_version then
@@ -30,20 +30,20 @@ Gem::Specification.new do |s|
     s.add_runtime_dependency(%q<activesupport>.freeze, [">= 3.0"])
     s.add_development_dependency(%q<erubi>.freeze, ["~> 1.6"])
     s.add_development_dependency(%q<erubis>.freeze, ["~> 2.7"])
-    s.add_runtime_dependency(%q<graphql>.freeze, ["~> 1.8"])
+    s.add_runtime_dependency(%q<graphql>.freeze, [">= 0"])
     s.add_development_dependency(%q<minitest>.freeze, ["~> 5.9"])
     s.add_development_dependency(%q<rake>.freeze, ["~> 11.2"])
     s.add_development_dependency(%q<rubocop>.freeze, ["~> 0.55"])
-    s.add_development_dependency(%q<rubocop-github>.freeze, ["~> 0.10"])
+    s.add_development_dependency(%q<rubocop-github>.freeze, ["~> 0.10", "<= 0.16.0"])
   else
     s.add_dependency(%q<actionpack>.freeze, [">= 3.2.22"])
     s.add_dependency(%q<activesupport>.freeze, [">= 3.0"])
     s.add_dependency(%q<erubi>.freeze, ["~> 1.6"])
     s.add_dependency(%q<erubis>.freeze, ["~> 2.7"])
-    s.add_dependency(%q<graphql>.freeze, ["~> 1.8"])
+    s.add_dependency(%q<graphql>.freeze, [">= 0"])
     s.add_dependency(%q<minitest>.freeze, ["~> 5.9"])
     s.add_dependency(%q<rake>.freeze, ["~> 11.2"])
     s.add_dependency(%q<rubocop>.freeze, ["~> 0.55"])
-    s.add_dependency(%q<rubocop-github>.freeze, ["~> 0.10"])
+    s.add_dependency(%q<rubocop-github>.freeze, ["~> 0.10", "<= 0.16.0"])
   end
 end
diff --git a/lib/graphql/client.rb b/lib/graphql/client.rb
index d46f3fc..83bad2d 100644
--- a/lib/graphql/client.rb
+++ b/lib/graphql/client.rb
@@ -49,12 +49,12 @@ module GraphQL
       when GraphQL::Schema, Class
         schema
       when Hash
-        GraphQL::Schema::Loader.load(schema)
+        GraphQL::Schema.from_introspection(schema)
       when String
         if schema.end_with?(".json") && File.exist?(schema)
           load_schema(File.read(schema))
         elsif schema =~ /\A\s*{/
-          load_schema(JSON.parse(schema))
+          load_schema(JSON.parse(schema, freeze: true))
         end
       else
         if schema.respond_to?(:execute)
@@ -97,10 +97,31 @@ module GraphQL
       @document_tracking_enabled = false
       @allow_dynamic_queries = false
       @enforce_collocated_callers = enforce_collocated_callers
-
+      if schema.is_a?(Class)
+        @possible_types = schema.possible_types
+      end
       @types = Schema.generate(@schema)
     end
 
+    # A cache of the schema's merged possible types
+    # @param type_condition [Class, String] a type definition or type name
+    def possible_types(type_condition = nil)
+      if type_condition
+        if defined?(@possible_types)
+          if type_condition.respond_to?(:graphql_name)
+            type_condition = type_condition.graphql_name
+          end
+          @possible_types[type_condition]
+        else
+          @schema.possible_types(type_condition)
+        end
+      elsif defined?(@possible_types)
+        @possible_types
+      else
+        @schema.possible_types(type_condition)
+      end
+    end
+
     def parse(str, filename = nil, lineno = nil)
       if filename.nil? && lineno.nil?
         location = caller_locations(1, 1).first
@@ -135,11 +156,7 @@ module GraphQL
           # which corresponds to the spread.
           # We depend on ActiveSupport to either find the already-loaded
           # constant, or to load the constant by name
-          begin
-            fragment = ActiveSupport::Inflector.constantize(const_name)
-          rescue NameError
-            fragment = nil
-          end
+          fragment = ActiveSupport::Inflector.safe_constantize(const_name)
 
           case fragment
           when FragmentDefinition
@@ -173,12 +190,8 @@ module GraphQL
 
       doc.definitions.each do |node|
         if node.name.nil?
-          if node.respond_to?(:merge) # GraphQL 1.9 +
-            node_with_name = node.merge(name: "__anonymous__")
-            doc = doc.replace_child(node, node_with_name)
-          else
-            node.name = "__anonymous__"
-          end
+          node_with_name = node.merge(name: "__anonymous__")
+          doc = doc.replace_child(node, node_with_name)
         end
       end
 
@@ -200,37 +213,13 @@ module GraphQL
         raise error
       end
 
-      definitions = {}
-      doc.definitions.each do |node|
-        sliced_document = Language::DefinitionSlice.slice(document_dependencies, node.name)
-        definition = Definition.for(
-          client: self,
-          ast_node: node,
-          document: sliced_document,
-          source_document: doc,
-          source_location: source_location
-        )
-        definitions[node.name] = definition
-      end
+      definitions = sliced_definitions(document_dependencies, doc, source_location: source_location)
 
-      if @document.respond_to?(:merge) # GraphQL 1.9+
-        visitor = RenameNodeVisitor.new(document_dependencies, definitions: definitions)
-        visitor.visit
-      else
-        name_hook = RenameNodeHook.new(definitions)
-        visitor = Language::Visitor.new(document_dependencies)
-        visitor[Language::Nodes::FragmentDefinition].leave << name_hook.method(:rename_node)
-        visitor[Language::Nodes::OperationDefinition].leave << name_hook.method(:rename_node)
-        visitor[Language::Nodes::FragmentSpread].leave << name_hook.method(:rename_node)
-        visitor.visit
-      end
+      visitor = RenameNodeVisitor.new(document_dependencies, definitions: definitions)
+      visitor.visit
 
       if document_tracking_enabled
-        if @document.respond_to?(:merge) # GraphQL 1.9+
-          @document = @document.merge(definitions: @document.definitions + doc.definitions)
-        else
-          @document.definitions.concat(doc.definitions)
-        end
+        @document = @document.merge(definitions: @document.definitions + doc.definitions)
       end
 
       if definitions["__anonymous__"]
@@ -276,27 +265,9 @@ module GraphQL
       end
     end
 
-    class RenameNodeHook
-      def initialize(definitions)
-        @definitions = definitions
-      end
-
-      def rename_node(node, _parent)
-        definition = @definitions[node.name]
-        if definition
-          node.extend(LazyName)
-          node._definition = definition
-        end
-      end
-    end
-
     # Public: A wrapper to use the more-efficient `.get_type` when it's available from GraphQL-Ruby (1.10+)
     def get_type(type_name)
-      if @schema.respond_to?(:get_type)
-        @schema.get_type(type_name)
-      else
-        @schema.types[type_name]
-      end
+      @schema.get_type(type_name)
     end
 
     # Public: Create operation definition from a fragment definition.
@@ -424,6 +395,44 @@ module GraphQL
 
     private
 
+    def sliced_definitions(document_dependencies, doc, source_location:)
+      dependencies = document_dependencies.definitions.map do |node|
+        [node.name, find_definition_dependencies(node)]
+      end.to_h
+
+      doc.definitions.map do |node|
+        deps = Set.new
+        definitions = document_dependencies.definitions.map { |x| [x.name, x] }.to_h
+
+        queue = [node.name]
+        while name = queue.shift
+          next if deps.include?(name)
+          deps.add(name)
+          queue.concat dependencies[name]
+        end
+
+        definitions = document_dependencies.definitions.select { |x| deps.include?(x.name)  }
+        sliced_document = Language::Nodes::Document.new(definitions: definitions)
+        definition = Definition.for(
+          client: self,
+          ast_node: node,
+          document: sliced_document,
+          source_document: doc,
+          source_location: source_location
+        )
+
+        [node.name, definition]
+      end.to_h
+    end
+
+    def find_definition_dependencies(node)
+      names = []
+      visitor = Language::Visitor.new(node)
+      visitor[Language::Nodes::FragmentSpread] << -> (node, parent) { names << node.name }
+      visitor.visit
+      names.uniq
+    end
+
     def deep_freeze_json_object(obj)
       case obj
       when String
diff --git a/lib/graphql/client/collocated_enforcement.rb b/lib/graphql/client/collocated_enforcement.rb
index fa804b3..ef484f5 100644
--- a/lib/graphql/client/collocated_enforcement.rb
+++ b/lib/graphql/client/collocated_enforcement.rb
@@ -12,6 +12,8 @@ module GraphQL
 
     # Enforcements collocated object access best practices.
     module CollocatedEnforcement
+      extend self
+
       # Public: Ignore collocated caller enforcement for the scope of the block.
       def allow_noncollocated_callers
         Thread.current[:query_result_caller_location_ignore] = true
@@ -20,6 +22,23 @@ module GraphQL
         Thread.current[:query_result_caller_location_ignore] = nil
       end
 
+      def verify_collocated_path(location, path, method = "method")
+        return yield if Thread.current[:query_result_caller_location_ignore]
+
+        if (location.path != path) && !(WHITELISTED_GEM_NAMES.any? { |g| location.path.include?("gems/#{g}") })
+          error = NonCollocatedCallerError.new("#{method} was called outside of '#{path}' https://git.io/v1syX")
+          error.set_backtrace(caller(2))
+          raise error
+        end
+
+        begin
+          Thread.current[:query_result_caller_location_ignore] = true
+          yield
+        ensure
+          Thread.current[:query_result_caller_location_ignore] = nil
+        end
+      end
+
       # Internal: Decorate method with collocated caller enforcement.
       #
       # mod - Target Module/Class
@@ -31,21 +50,9 @@ module GraphQL
         mod.prepend(Module.new do
           methods.each do |method|
             define_method(method) do |*args, &block|
-              return super(*args, &block) if Thread.current[:query_result_caller_location_ignore]
-
-              locations = caller_locations(1, 1)
-
-              if (locations.first.path != path) && !(caller_locations.any? { |cl| WHITELISTED_GEM_NAMES.any? { |g| cl.path.include?("gems/#{g}") } })
-                error = NonCollocatedCallerError.new("#{method} was called outside of '#{path}' https://git.io/v1syX")
-                error.set_backtrace(caller(1))
-                raise error
-              end
-
-              begin
-                Thread.current[:query_result_caller_location_ignore] = true
+              location = caller_locations(1, 1)[0]
+              CollocatedEnforcement.verify_collocated_path(location, path, method) do
                 super(*args, &block)
-              ensure
-                Thread.current[:query_result_caller_location_ignore] = nil
               end
             end
           end
diff --git a/lib/graphql/client/definition.rb b/lib/graphql/client/definition.rb
index 9078743..8df0745 100644
--- a/lib/graphql/client/definition.rb
+++ b/lib/graphql/client/definition.rb
@@ -115,9 +115,24 @@ module GraphQL
           else
             cast_object(obj)
           end
+        when GraphQL::Client::Schema::ObjectType::WithDefinition
+          case obj
+          when schema_class.klass
+            if obj._definer == schema_class
+              obj
+            else
+              cast_object(obj)
+            end
+          when nil
+            nil
+          when Hash
+            schema_class.new(obj, errors)
+          else
+            cast_object(obj)
+          end
         when GraphQL::Client::Schema::ObjectType
           case obj
-          when NilClass, schema_class
+          when nil, schema_class
             obj
           when Hash
             schema_class.new(obj, errors)
@@ -144,8 +159,8 @@ module GraphQL
 
         def cast_object(obj)
           if obj.class.is_a?(GraphQL::Client::Schema::ObjectType)
-            unless obj.class._spreads.include?(definition_node.name)
-              raise TypeError, "#{definition_node.name} is not included in #{obj.class.source_definition.name}"
+            unless obj._spreads.include?(definition_node.name)
+              raise TypeError, "#{definition_node.name} is not included in #{obj.source_definition.name}"
             end
             schema_class.cast(obj.to_h, obj.errors)
           else
diff --git a/lib/graphql/client/http.rb b/lib/graphql/client/http.rb
index 6201ffe..700aaa8 100644
--- a/lib/graphql/client/http.rb
+++ b/lib/graphql/client/http.rb
@@ -7,7 +7,7 @@ module GraphQL
   class Client
     # Public: Basic HTTP network adapter.
     #
-    #   GraphQL::Client::Client.new(
+    #   GraphQL::Client.new(
     #     execute: GraphQL::Client::HTTP.new("http://graphql-swapi.parseapp.com/")
     #   )
     #
diff --git a/lib/graphql/client/schema/interface_type.rb b/lib/graphql/client/schema/interface_type.rb
index 5562d24..2fb8853 100644
--- a/lib/graphql/client/schema/interface_type.rb
+++ b/lib/graphql/client/schema/interface_type.rb
@@ -21,7 +21,7 @@ module GraphQL
         end
 
         def define_class(definition, ast_nodes)
-          possible_type_names = definition.client.schema.possible_types(type).map(&:graphql_name)
+          possible_type_names = definition.client.possible_types(type).map(&:graphql_name)
           possible_types = possible_type_names.map { |concrete_type_name|
             schema_module.get_class(concrete_type_name).define_class(definition, ast_nodes)
           }
diff --git a/lib/graphql/client/schema/object_type.rb b/lib/graphql/client/schema/object_type.rb
index f86a57a..4202a32 100644
--- a/lib/graphql/client/schema/object_type.rb
+++ b/lib/graphql/client/schema/object_type.rb
@@ -16,6 +16,53 @@ module GraphQL
 
             define_singleton_method(:type) { type }
             define_singleton_method(:fields) { fields }
+
+            const_set(:READERS, {})
+            const_set(:PREDICATES, {})
+          end
+        end
+
+        class WithDefinition
+          include BaseType
+          include ObjectType
+
+          EMPTY_SET = Set.new.freeze
+
+          attr_reader :klass, :defined_fields, :definition
+
+          def type
+            @klass.type
+          end
+
+          def fields
+            @klass.fields
+          end
+
+          def spreads
+            if defined?(@spreads)
+              @spreads
+            else
+              EMPTY_SET
+            end
+          end
+
+          def initialize(klass, defined_fields, definition, spreads)
+            @klass = klass
+            @defined_fields = defined_fields.map do |k, v|
+              [-k.to_s, v]
+            end.to_h
+            @definition = definition
+            @spreads = spreads unless spreads.empty?
+
+            @defined_fields.keys.each do |attr|
+              name = ActiveSupport::Inflector.underscore(attr)
+              @klass::READERS[:"#{name}"] ||= attr
+              @klass::PREDICATES[:"#{name}?"] ||= attr
+            end
+          end
+
+          def new(data = {}, errors = Errors.new)
+            @klass.new(data, errors, self)
           end
         end
 
@@ -46,56 +93,9 @@ module GraphQL
             field_classes[result_name.to_sym] = schema_module.define_class(definition, field_ast_nodes, field_return_type)
           end
 
-          klass = Class.new(self)
-          klass.define_fields(field_classes)
-          klass.instance_variable_set(:@source_definition, definition)
-          klass.instance_variable_set(:@_spreads, definition.indexes[:spreads][ast_nodes.first])
-
-          if definition.client.enforce_collocated_callers
-            keys = field_classes.keys.map { |key| ActiveSupport::Inflector.underscore(key) }
-            Client.enforce_collocated_callers(klass, keys, definition.source_location[0])
-          end
-
-          klass
-        end
-
-        PREDICATE_CACHE = Hash.new { |h, name|
-          h[name] = -> { @data[name] ? true : false }
-        }
+          spreads = definition.indexes[:spreads][ast_nodes.first]
 
-        METHOD_CACHE = Hash.new { |h, key|
-          h[key] = -> {
-            name = key.to_s
-            type = self.class::FIELDS[key]
-            @casted_data.fetch(name) do
-              @casted_data[name] = type.cast(@data[name], @errors.filter_by_path(name))
-            end
-          }
-        }
-
-        MODULE_CACHE = Hash.new do |h, fields|
-          h[fields] = Module.new do
-            fields.each do |name|
-              GraphQL::Client::Schema::ObjectType.define_cached_field(name, self)
-            end
-          end
-        end
-
-        FIELDS_CACHE = Hash.new { |h, k| h[k] = k }
-
-        def define_fields(fields)
-          const_set :FIELDS, FIELDS_CACHE[fields]
-          mod = MODULE_CACHE[fields.keys.sort]
-          include mod
-        end
-
-        def self.define_cached_field(name, ctx)
-          key = name
-          name = -name.to_s
-          method_name = ActiveSupport::Inflector.underscore(name)
-
-          ctx.send(:define_method, method_name, &METHOD_CACHE[key])
-          ctx.send(:define_method, "#{method_name}?", &PREDICATE_CACHE[name])
+          WithDefinition.new(self, field_classes, definition, spreads)
         end
 
         def define_field(name, type)
@@ -134,9 +134,8 @@ module GraphQL
             continue_selection = if selected_ast_node.type.nil?
               true
             else
-              schema = definition.client.schema
               type_condition = definition.client.get_type(selected_ast_node.type.name)
-              applicable_types = schema.possible_types(type_condition)
+              applicable_types = definition.client.possible_types(type_condition)
               # continue if this object type is one of the types matching the fragment condition
               applicable_types.include?(type)
             end
@@ -150,10 +149,8 @@ module GraphQL
             fragment_definition = definition.document.definitions.find do |defn|
               defn.is_a?(GraphQL::Language::Nodes::FragmentDefinition) && defn.name == selected_ast_node.name
             end
-
-            schema = definition.client.schema
             type_condition = definition.client.get_type(fragment_definition.type.name)
-            applicable_types = schema.possible_types(type_condition)
+            applicable_types = definition.client.possible_types(type_condition)
             # continue if this object type is one of the types matching the fragment condition
             continue_selection = applicable_types.include?(type)
 
@@ -177,17 +174,16 @@ module GraphQL
       end
 
       class ObjectClass
-        module ClassMethods
-          attr_reader :source_definition
-          attr_reader :_spreads
-        end
-
-        extend ClassMethods
-
-        def initialize(data = {}, errors = Errors.new)
+        def initialize(data = {}, errors = Errors.new, definer = nil)
           @data = data
           @casted_data = {}
           @errors = errors
+
+          # If we are not provided a definition, we can use this empty default
+          definer ||= ObjectType::WithDefinition.new(self.class, {}, nil, [])
+
+          @definer = definer
+          @enforce_collocated_callers = source_definition && source_definition.client.enforce_collocated_callers
         end
 
         # Public: Returns the raw response data
@@ -197,42 +193,85 @@ module GraphQL
           @data
         end
 
-        # Public: Return errors associated with data.
-        #
-        # Returns Errors collection.
-        attr_reader :errors
+        def _definer
+          @definer
+        end
 
-        def method_missing(*args)
-          super
-        rescue NoMethodError => e
-          type = self.class.type
+        def _spreads
+          @definer.spreads
+        end
 
-          if ActiveSupport::Inflector.underscore(e.name.to_s) != e.name.to_s
-            raise e
-          end
+        def source_definition
+          @definer.definition
+        end
 
-          all_fields = type.respond_to?(:all_fields) ? type.all_fields : type.fields.values
-          field = all_fields.find do |f|
-            f.name == e.name.to_s || ActiveSupport::Inflector.underscore(f.name) == e.name.to_s
+        def respond_to_missing?(name, priv)
+          if (attr = self.class::READERS[name]) || (attr = self.class::PREDICATES[name])
+            @definer.defined_fields.key?(attr) || super
+          else
+            super
           end
+        end
 
-          unless field
-            raise UnimplementedFieldError, "undefined field `#{e.name}' on #{type.graphql_name} type. https://git.io/v1y3m"
+        # Public: Return errors associated with data.
+        #
+        # It's possible to define "errors" as a field. Ideally this shouldn't
+        # happen, but if it does we should prefer the field rather than the
+        # builtin error type.
+        #
+        # Returns Errors collection.
+        def errors
+          if type = @definer.defined_fields["errors"]
+            read_attribute("errors", type)
+          else
+            @errors
           end
+        end
 
-          if @data.key?(field.name)
-            error_class = ImplicitlyFetchedFieldError
-            message = "implicitly fetched field `#{field.name}' on #{type} type. https://git.io/v1yGL"
+        def method_missing(name, *args)
+          if (attr = self.class::READERS[name]) && (type = @definer.defined_fields[attr])
+            if @enforce_collocated_callers
+              verify_collocated_path do
+                read_attribute(attr, type)
+              end
+            else
+              read_attribute(attr, type)
+            end
+          elsif (attr = self.class::PREDICATES[name]) && @definer.defined_fields[attr]
+            has_attribute?(attr)
           else
-            error_class = UnfetchedFieldError
-            message = "unfetched field `#{field.name}' on #{type} type. https://git.io/v1y3U"
-          end
+            begin
+              super
+            rescue NoMethodError => e
+              type = self.class.type
 
-          raise error_class, message
+              if ActiveSupport::Inflector.underscore(e.name.to_s) != e.name.to_s
+                raise e
+              end
+
+              all_fields = type.respond_to?(:all_fields) ? type.all_fields : type.fields.values
+              field = 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.graphql_name} type. https://git.io/v1y3m"
+              end
+
+              if @data.key?(field.name)
+                raise ImplicitlyFetchedFieldError, "implicitly fetched field `#{field.name}' on #{type} type. https://git.io/v1yGL"
+              else
+                raise UnfetchedFieldError, "unfetched field `#{field.name}' on #{type} type. https://git.io/v1y3U"
+              end
+            end
+          end
         end
 
         def inspect
-          parent = self.class.ancestors.select { |m| m.is_a?(ObjectType) }.last
+          parent = self.class
+          until parent.superclass == ObjectClass
+            parent = parent.superclass
+          end
 
           ivars = @data.map { |key, value|
             if value.is_a?(Hash) || value.is_a?(Array)
@@ -247,6 +286,26 @@ module GraphQL
           buf << ">"
           buf
         end
+
+        private
+
+        def verify_collocated_path
+          location = caller_locations(2, 1)[0]
+
+          CollocatedEnforcement.verify_collocated_path(location, source_definition.source_location[0]) do
+            yield
+          end
+        end
+
+        def read_attribute(attr, type)
+          @casted_data.fetch(attr) do
+            @casted_data[attr] = type.cast(@data[attr], @errors.filter_by_path(attr))
+          end
+        end
+
+        def has_attribute?(attr)
+          !!@data[attr]
+        end
       end
     end
   end
diff --git a/lib/graphql/client/schema/union_type.rb b/lib/graphql/client/schema/union_type.rb
index bb112e2..159219f 100644
--- a/lib/graphql/client/schema/union_type.rb
+++ b/lib/graphql/client/schema/union_type.rb
@@ -21,7 +21,7 @@ module GraphQL
         end
 
         def define_class(definition, ast_nodes)
-          possible_type_names = definition.client.schema.possible_types(type).map(&:graphql_name)
+          possible_type_names = definition.client.possible_types(type).map(&:graphql_name)
           possible_types = possible_type_names.map { |concrete_type_name|
             schema_module.get_class(concrete_type_name).define_class(definition, ast_nodes)
           }
diff --git a/lib/graphql/client/view_module.rb b/lib/graphql/client/view_module.rb
index 3f7e9ab..302b8a7 100644
--- a/lib/graphql/client/view_module.rb
+++ b/lib/graphql/client/view_module.rb
@@ -133,7 +133,7 @@ module GraphQL
 
         remove_const(name) if placeholder
         const_set(name, mod)
-        mod.unloadable
+        mod.unloadable if mod.respond_to?(:unloadable)
         mod
       end