Документация 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"
}
]
Это нормально, за исключением того, что у него есть один дополнительный уровень косвенности, которого я бы предпочел избежать.





Коллега указал мне на следующий код, который работает, но, боюсь, я до сих пор не до конца понимаю все, что происходит. Но я постараюсь объяснить.
Насколько я понимаю, идея с скалярными типами заключается в том, что если у вас есть собственный фундаментальный тип (обычно это не сложный объект, а всего лишь один элемент данных), который не является ни одним из базовых встроенных, вы можете определить его как скаляр. (Вы можете получить представление о примерах скаляров из проекта 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.
Ваш запрос очень прост:
arguments {
name
type
description
position
optional
value // <-- this is the relevant bit
}
и вы просто возвращаете все значения в любом типе, который они могут иметь. Ваш интерфейс должен справиться с этим!
Вероятно, это можно было бы улучшить, чтобы немного сузить область, где он сломается, когда встретится неожиданный тип.
К сожалению, я обнаружил, что с драгоценным камнем Ruby graphql не очень легко работать. Однако работа с реализацией Elixir доставляет удовольствие. Но это только мой опыт, очевидно.