From e97cb5bad9efa6fb8daa4edec0f115c93c30275b Mon Sep 17 00:00:00 2001 From: Elvin Efendiev Date: Thu, 22 Feb 2024 08:14:43 -0500 Subject: [PATCH] keep things simple: instead of redefining a method, simply remove it --- lib/graphql/client/schema/object_type.rb | 100 ++++++++++------------- test/test_query_result.rb | 2 +- 2 files changed, 42 insertions(+), 60 deletions(-) diff --git a/lib/graphql/client/schema/object_type.rb b/lib/graphql/client/schema/object_type.rb index 2b343dc..a18f659 100644 --- a/lib/graphql/client/schema/object_type.rb +++ b/lib/graphql/client/schema/object_type.rb @@ -19,7 +19,6 @@ def self.new(type, fields = {}) const_set(:READERS, {}) const_set(:PREDICATES, {}) - const_set(:FIELDS_CONFLICTING_WITH_BASE_RUBY_METHODS, {}) end end @@ -28,7 +27,7 @@ class WithDefinition include ObjectType EMPTY_SET = Set.new.freeze - POSSIBLE_FIELDS_CONFLICTING_WITH_BASE_RUBY_METHODS = + BASE_RUBY_METHODS = (Object.methods + Kernel.methods).uniq.filter { |m| m.to_s =~ /^[a-zA-Z_][a-zA-Z_0-9]*$/ } .map(&:to_s).to_set.freeze @@ -62,9 +61,9 @@ def initialize(klass, defined_fields, definition, spreads) name = ActiveSupport::Inflector.underscore(attr) @klass::READERS[:"#{name}"] ||= attr @klass::PREDICATES[:"#{name}?"] ||= attr - if POSSIBLE_FIELDS_CONFLICTING_WITH_BASE_RUBY_METHODS.include?(name) - @klass::FIELDS_CONFLICTING_WITH_BASE_RUBY_METHODS[:"#{name}"] ||= attr - end + + @klass.undef_method(name) if BASE_RUBY_METHODS.include?(name) && @klass.method_defined?(name) + @klass.undef_method("#{name}?") if BASE_RUBY_METHODS.include?("#{name}?") && @klass.method_defined?("#{name}?") end end @@ -191,7 +190,6 @@ def initialize(data = {}, errors = Errors.new, definer = nil) @definer = definer @enforce_collocated_callers = source_definition && source_definition.client.enforce_collocated_callers - define_accessor_methods end # Public: Returns the raw response data @@ -238,9 +236,42 @@ def errors end def method_missing(name, *args) - try_fetch_attribute(name, args) - rescue NoMethodError => e - handle_no_method_error(e) + 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 + begin + super + rescue NoMethodError => e + type = self.class.type + + 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://github.com/github-community-projects/graphql-client/blob/master/guides/unimplemented-field-error.md" + end + + if @data.key?(field.name) + raise ImplicitlyFetchedFieldError, "implicitly fetched field `#{field.name}' on #{type} type. https://github.com/github-community-projects/graphql-client/blob/master/guides/implicitly-fetched-field-error.md" + else + raise UnfetchedFieldError, "unfetched field `#{field.name}' on #{type} type. https://github.com/github-community-projects/graphql-client/blob/master/guides/unfetched-field-error.md" + end + end + end end def inspect @@ -266,23 +297,13 @@ def inspect private def verify_collocated_path - location = caller_locations(3, 1)[0] + location = caller_locations(2, 1)[0] CollocatedEnforcement.verify_collocated_path(location, source_definition.source_location[0]) do yield end end - def define_accessor_methods - self.class::FIELDS_CONFLICTING_WITH_BASE_RUBY_METHODS.each do |k, v| - define_singleton_method(k) do - try_fetch_attribute(k, []) - rescue NoMethodError => e - handle_no_method_error(e) - end - end - end - def read_attribute(attr, type) @casted_data.fetch(attr) do @casted_data[attr] = type.cast(@data[attr], @errors.filter_by_path(attr)) @@ -292,45 +313,6 @@ def read_attribute(attr, type) def has_attribute?(attr) !!@data[attr] end - - def try_fetch_attribute(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 - raise NoMethodError.new("undefined method `#{name}` for #{self.class}", name, *args) - end - end - - def handle_no_method_error(e) - type = self.class.type - - 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://github.com/github-community-projects/graphql-client/blob/master/guides/unimplemented-field-error.md" - end - - if @data.key?(field.name) - raise ImplicitlyFetchedFieldError, "implicitly fetched field `#{field.name}' on #{type} type. https://github.com/github-community-projects/graphql-client/blob/master/guides/implicitly-fetched-field-error.md" - else - raise UnfetchedFieldError, "unfetched field `#{field.name}' on #{type} type. https://github.com/github-community-projects/graphql-client/blob/master/guides/unfetched-field-error.md" - end - end end end end diff --git a/test/test_query_result.rb b/test/test_query_result.rb index 9840351..ed6cd83 100644 --- a/test/test_query_result.rb +++ b/test/test_query_result.rb @@ -243,7 +243,7 @@ def test_define_simple_query_result GRAPHQL response = @client.query(Temp::Query) - assert response.data.me.respond_to?(:name) + refute response.data.me.respond_to?(:name) refute response.data.me.respond_to?(:company) person = Temp::Person.new(response.data.me)