فهرست منبع

Prepare GraphQL for Relay

Andrew Swistak 6 سال پیش
والد
کامیت
e7f1d9300a

+ 19 - 0
app/graphql/pokemon_trade_schema.rb

@@ -3,4 +3,23 @@
 class PokemonTradeSchema < GraphQL::Schema
   mutation(Types::MutationType)
   query(Types::QueryType)
+
+  def self.id_from_object(object, type_definition, _query_ctx)
+    GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id)
+  end
+
+  def self.object_from_id(id, _query_ctx)
+    type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
+    Object.const_get(type_name).find(item_id)
+  end
+
+  def self.resolve_type(_type, obj, _ctx)
+    case obj
+    when Pokemon
+      Types::PokemonType
+    else
+      Rails.logger.error("Unexpected object when resolving GraphQL type: #{obj}")
+      raise APIError::BaseError, 'Unexpected object when resolving GraphQL type'
+    end
+  end
 end

+ 52 - 0
app/graphql/resolvers/base_resolver.rb

@@ -2,6 +2,58 @@
 
 module Resolvers
   class BaseResolver < GraphQL::Schema::Resolver
+    def self.argument_with_plural(field, type, **args)
+      plural_sym = field.to_s.pluralize.to_sym
+      plurals[field] = plural_sym
+
+      argument(field, type, args)
+      argument(plural_sym, [type], args.dup.tap { |a| a[:required] = false })
+    end
+
+    def self.plurals
+      @plurals ||= {}
+    end
+
+    def plurals
+      self.class.plurals
+    end
+
+    def clobber_id_with_iid!(args)
+      args[:id] = args.delete(:iid) if args[:iid]
+    end
+
+    def clobber_ids_with_iids!(args)
+      args[:ids] = args.delete(:iids) if args[:iids]
+    end
+
+    def combine_plurals!(args)
+      plurals.each do |singular, plural|
+        args[singular] = [args[singular], *args.delete(plural)] if args[plural]
+      end
+    end
+
+    def decode_id(args)
+      if (id = args[:id])
+        _, iid = GraphQL::Schema::UniqueWithinType.decode(id)
+        args[:id] = iid
+      end
+    end
+
+    def decode_ids(args)
+      if (ids = args[:ids])
+        ids.map! { |i| GraphQL::Schema::UniqueWithinType.decode(i).last }
+        args[:ids] = ids
+      end
+    end
+
+    def prepare_args!(args)
+      decode_id(args)
+      decode_ids(args)
+      clobber_id_with_iid!(args)
+      clobber_ids_with_iids!(args)
+      combine_plurals!(args)
+    end
+
     def self.single
       @single ||= Class.new(self) do
         def resolve(**args)

+ 9 - 3
app/graphql/resolvers/pokemon_resolver.rb

@@ -2,11 +2,17 @@
 
 module Resolvers
   class PokemonResolver < BaseResolver
-    argument :id, GraphQL::ID_TYPE, required: false
-    argument :pokedex_number, GraphQL::INT_TYPE, required: false
-    argument :nickname, GraphQL::STRING_TYPE, required: false
+    description 'Fetches one or more pokemon'
+
+    argument_with_plural :id, GraphQL::STRING_TYPE, required: false
+    argument_with_plural :iid, GraphQL::ID_TYPE, required: false
+    argument_with_plural :pokedex_number, GraphQL::INT_TYPE, required: false
+    argument_with_plural :nickname, GraphQL::STRING_TYPE, required: false
+    argument :created_at, Types::TimeType, required: false
+    argument :updated_at, Types::TimeType, required: false
 
     def resolve(**args)
+      prepare_args!(args)
       Pokemon.where(args)
     end
   end

+ 8 - 1
app/graphql/types/pokemon_type.rb

@@ -2,8 +2,15 @@
 
 module Types
   class PokemonType < Types::BaseObject
-    field :id, GraphQL::ID_TYPE, null: false
+    description 'Returns a singular pokemon'
+
+    implements GraphQL::Types::Relay::BaseInterface
+    global_id_field :id
+
+    field :iid, GraphQL::ID_TYPE, null: false, method: :id
     field :nickname, GraphQL::STRING_TYPE, null: true
     field :pokedex_number, GraphQL::INT_TYPE, null: true
+    field :created_at, Types::TimeType, null: true
+    field :updated_at, Types::TimeType, null: true
   end
 end

+ 25 - 12
app/graphql/types/query_type.rb

@@ -1,15 +1,28 @@
 # frozen_string_literal: true
 
-class Types::QueryType < Types::BaseObject
-  description 'The root of this schema'
-
-  field :pokemon,
-        Types::PokemonType,
-        null: true,
-        resolver: Resolvers::PokemonResolver.single
-
-  field :many_pokemon,
-        [Types::PokemonType],
-        null: false,
-        resolver: Resolvers::PokemonResolver
+module Types
+  class QueryType < GraphQL::Schema::Object
+    description 'The root of this schema'
+
+    field :pokemon,
+          Types::PokemonType,
+          null: true,
+          resolver: Resolvers::PokemonResolver.single,
+          description: 'Fetches exactly zero or one pokemon'
+
+    field :many_pokemon,
+          [Types::PokemonType],
+          null: false,
+          resolver: Resolvers::PokemonResolver,
+          description: 'Fetches one or more pokemon'
+
+    field :pokemon_connection,
+          Types::PokemonType.connection_type,
+          null: false,
+          resolver: Resolvers::PokemonResolver,
+          description: 'Fetches one or more pokemon for Relay'
+
+    add_field(GraphQL::Types::Relay::NodeField)
+    add_field(GraphQL::Types::Relay::NodesField)
+  end
 end

+ 15 - 0
app/graphql/types/time_type.rb

@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+  class TimeType < BaseScalar
+    description 'Time represented in ISO 8601'
+
+    def self.coerce_input(value, _ctx)
+      Time.parse(value).in_time_zone
+    end
+
+    def self.coerce_result(value, _ctx)
+      value.iso8601
+    end
+  end
+end