# frozen_string_literal: true require 'irb/ruby-token' module GraphQL class Schema class Member # Shared code for Object and Interface module HasFields # Add a field to this object or interface with the given definition # @see {GraphQL::Schema::Field#initialize} for method signature # @return [GraphQL::Schema::Field] def field(*args, **kwargs, &block) field_defn = field_class.from_options(*args, owner: self, **kwargs, &block) add_field(field_defn) field_defn end # @return [Hash GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields def fields # Local overrides take precedence over inherited fields all_fields = {} ancestors.reverse_each do |ancestor| if ancestor.respond_to?(:own_fields) all_fields.merge!(ancestor.own_fields) end end all_fields end def get_field(field_name) if (f = own_fields[field_name]) f else for ancestor in ancestors if ancestor.respond_to?(:own_fields) && f = ancestor.own_fields[field_name] return f end end nil end end # A list of Ruby keywords. # # @api private RUBY_KEYWORDS = RubyToken::TokenDefinitions.select { |definition| definition[1] == RubyToken::TkId } .map { |definition| definition[2] } .compact # A list of GraphQL-Ruby keywords. # # @api private GRAPHQL_RUBY_KEYWORDS = [:context, :object, :method] # A list of field names that we should advise users to pick a different # resolve method name. # # @api private CONFLICT_FIELD_NAMES = Set.new(GRAPHQL_RUBY_KEYWORDS + RUBY_KEYWORDS) # Register this field with the class, overriding a previous one if needed. # @param field_defn [GraphQL::Schema::Field] # @return [void] def add_field(field_defn) if CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.method_conflict_warning? warn "#{self.graphql_name}'s `field :#{field_defn.name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.resolver_method}` and `def resolve_#{field_defn.resolver_method}`). Or use `method_conflict_warning: false` to suppress this warning." end own_fields[field_defn.name] = field_defn nil end # @return [Class] The class to initialize when adding fields to this kind of schema member def field_class(new_field_class = nil) if new_field_class @field_class = new_field_class elsif @field_class @field_class elsif self.is_a?(Class) superclass.respond_to?(:field_class) ? superclass.field_class : GraphQL::Schema::Field else ancestor = ancestors[1..-1].find { |a| a.respond_to?(:field_class) && a.field_class } ancestor ? ancestor.field_class : GraphQL::Schema::Field end end def global_id_field(field_name) id_resolver = GraphQL::Relay::GlobalIdResolve.new(type: self) field field_name, "ID", null: false define_method(field_name) do id_resolver.call(object, {}, context) end end # @return [Array] Fields defined on this class _specifically_, not parent classes def own_fields @own_fields ||= {} end end end end end