Skip to content

Commit

Permalink
keep things simple: instead of redefining a method, simply remove it
Browse files Browse the repository at this point in the history
  • Loading branch information
ElvinEfendi committed Feb 22, 2024
1 parent 820356d commit e97cb5b
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 60 deletions.
100 changes: 41 additions & 59 deletions lib/graphql/client/schema/object_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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))
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/test_query_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit e97cb5b

Please sign in to comment.