Использование списка в типе объединения GraphQL (в Ruby)

Документация GraphQL Ruby показывает, как определить тип объединения:

class Types::CommentSubject < Types::BaseUnion
  description "Objects which may be commented on"
  possible_types Types::Post, Types::Image

  # Optional: if this method is defined, it will override `Schema.resolve_type`
  def self.resolve_type(object, context)
    if object.is_a?(BlogPost)
      Types::Post
    else
      Types::Image
    end
  end
end

и он показывает, как объявить, что поле является списком вне объединения:

# A field returning a list type:
# Equivalent to `aliases: [String!]` above
field :aliases, [String]

# An argument which accepts a list type:
argument :categories, [Types::PostCategory], required: false

но я не могу понять, как использовать список в качестве возможного типа, которым может быть член союза.

Мой код выглядит примерно так:

class Types::ArgumentValueType < Types::BaseUnion
  possible_types GraphQL::Types::String, GraphQL::Types::Boolean, GraphQL::Types::Int

  def self.resolve_type(object, _context)
    if object.is_a?(String)
      GraphQL::Types::String
    elsif object.is_a?(Array)
      [GraphQL::Types::String]
    elsif object.is_a?(FalseClass)
      GraphQL::Types::Boolean
    elsif object.is_a?(TrueClass)
      GraphQL::Types::Boolean
    elsif object.is_a?(Integer)
      GraphQL::Types::Int
    end
  end
end

… что работает, за исключением того, что когда это массив, это значение возвращается в виде строки. В GraphiQL это выглядит так (мы смотрим на поле value):

{
  "name": "top_box",
  "type": "Array",
  "description": "The chosen values of the scale which should be combined",
  "position": 2,
  "optional": false,
  "value": "[\"8\", \"9\", \"10\"]"
}

Мы потенциально могли бы проанализировать это в клиенте, но в идеале я бы хотел, чтобы это был массив строк, например:

{
  "name": "top_box",
  "type": "Array",
  "description": "The chosen values of the scale which should be combined",
  "position": 2,
  "optional": false,
  "value": [
    "8",
    "9",
    "10"
  ]
},

Но я не понимаю, как это определить, и единственная информация, которую я смог найти, — это краткий комментарий в этом ответе на «GraphQL Union in Union», который, кажется, предполагает, что это может быть невозможно.

Ошибки

Если я попытаюсь добавить [GraphQL::Types::String] к possible_types, я получу

undefined method `graphql_name' for [GraphQL::Types::String]:Array

Если я попытаюсь добавить GraphQL::Schema::List.new(GraphQL::Types::String) к possible_types, я получу

undefined method `directives' for #<GraphQL::Schema::List:0x000000010f690950 @of_type=GraphQL::Types::String>

и если я попытаюсь заменить [GraphQL::Types::String] (под elsif object.is_a?(Array)) на GraphQL::Schema::List.new(GraphQL::Types::String), то я получу

.resolve_type should return a type definition, but got #<GraphQL::Schema::List:0x000000010f9fd6b0
@of_type=GraphQL::Types::String> (GraphQL::Schema::List) from `resolve_type(Types::ArgumentValueType,
[\"8\", \"9\", \"10\"], #<GraphQL::Query::Context:0x000000010f8ed018>)`

Обновлять

Мне удалось сделать улучшение, добавив класс-оболочку:

# frozen_string_literal: true

module Types
  class ListOfStringsType < Types::BaseObject
    field :values, [String]
  end
end

Затем с помощью запроса GraphQL, который выглядит примерно так:

arguments {
  name
  type
  description
  position
  optional
  value {
    ... on ListOfStrings {
      values
    }
  }
}

Он производит такой вывод

"arguments": [
  {
    "name": "top_box",
    "type": "Array",
    "description": "The chosen values of the scale which should be combined",
    "position": 2,
    "optional": false,
    "value": {
      "values": [
        "8",
        "9",
        "10"
      ]
    }
  },
  {
    "name": "measure",
    "type": "Measure",
    "description": "The name of the measure to be \"top-boxed\"",
    "position": 1,
    "optional": false,
    "value": "unique_and_different"
  }
]

Это нормально, за исключением того, что у него есть один дополнительный уровень косвенности, которого я бы предпочел избежать.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
226
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Коллега указал мне на следующий код, который работает, но, боюсь, я до сих пор не до конца понимаю все, что происходит. Но я постараюсь объяснить.

Используйте скаляр

Насколько я понимаю, идея с скалярными типами заключается в том, что если у вас есть собственный фундаментальный тип (обычно это не сложный объект, а всего лишь один элемент данных), который не является ни одним из базовых встроенных, вы можете определить его как скаляр. (Вы можете получить представление о примерах скаляров из проекта graphql-scalars). В конечном счете, конечно, все является строкой по сети, но в вашем бэкенде вы определяете, как сериализовать ваш тип, а во внешнем интерфейсе вы определяете, как его десериализовать, и, конечно, наоборот для записи.

Итак, вы заменяете содержимое вашего файла arguments_value_type.rb следующим.

class Types::ArgumentValueType < Types::BaseScalar
  description "A value"

  def self.coerce_input(input_value, _context)
    input_value
  end

  def self.coerce_result(ruby_value, _context)
    ruby_value
  end
end

Как видно из справочника Скаляры:

  • self.coerce_input принимает ввод GraphQL и преобразует его в значение Ruby.
  • self.coerce_result принимает возвращаемое значение поля и подготавливает его для ответа GraphQL в формате JSON.

…другими словами, никакой конверсии в любом случае.

Тогда вы можете засунуть туда что угодно, и оно просто выйдет как есть. Предполагая, что это можно представить в JSON.

GraphQL-запрос

Ваш запрос очень прост:

arguments {
  name
  type
  description
  position
  optional
  value       // <-- this is the relevant bit
}

и вы просто возвращаете все значения в любом типе, который они могут иметь. Ваш интерфейс должен справиться с этим!

Будущие улучшения

Вероятно, это можно было бы улучшить, чтобы немного сузить область, где он сломается, когда встретится неожиданный тип.

К сожалению, я обнаружил, что с драгоценным камнем Ruby graphql не очень легко работать. Однако работа с реализацией Elixir доставляет удовольствие. Но это только мой опыт, очевидно.

Eyeslandic 06.10.2022 23:43

Другие вопросы по теме