Skip to content

Instantly share code, notes, and snippets.

@amkisko
Last active April 6, 2020 20:17
Show Gist options
  • Save amkisko/8e5b9eba61878713188c0242a325e279 to your computer and use it in GitHub Desktop.
Save amkisko/8e5b9eba61878713188c0242a325e279 to your computer and use it in GitHub Desktop.
GraphQL Ruby List extension
class FieldExtensions::ListExtension < GraphQL::Schema::FieldExtension
def apply
object_type = field.type.unwrap
list_type_name = object_type.graphql_name
namespace = options[:namespace] || "default"
list_type =
Class.new(Objects::BaseObject) do
self.graphql_name("#{namespace}_#{list_type_name}_list")
self.field(:rows, [object_type], null: true)
self.field(:totalCount, Integer, null: true)
end
field.type = list_type
field.argument :offset, Integer, required: false
field.argument :limit, Integer, required: false
field.argument :efilter, GraphQL::Types::JSON, required: false
field.argument :order, GraphQL::Types::JSON, required: false
field.argument :search, String, required: false
end
def after_resolve(value:, arguments:, context:, **_kargs)
reduce_query(context, value, **arguments)
rescue StandardError => e
ServerError.log!(e)
message =
if Rails.env.development?
e.message
else
"Query arguments error"
end
context_error(context, message: message, code: "QUERY_ARGUMENTS_ERROR")
end
private def query_efilter(context, query, value)
return query if query.nil? || value.blank?
# rubocop:disable GitlabSecurity/JsonSerialization
value = parse_value(value.as_json)
# rubocop:enable GitlabSecurity/JsonSerialization
return query if value.blank?
# NOTE: https://gist.github.com/amkisko/bdb164af4f98bc97db0b5bb62e533c9a
query.efilter(value)
rescue JSON::ParserError
context_error(context, code: "QUERY_EFILTER_INVALID_VALUE")
end
private def query_order(context, query, value)
return query if query.nil? || value.blank?
# rubocop:disable GitlabSecurity/JsonSerialization
value = parse_value(value.as_json)
# rubocop:enable GitlabSecurity/JsonSerialization
return query if value.blank?
# NOTE: https://gist.github.com/amkisko/369fb758fecf1ebd8450e4e30fbe919f
value.reduce(query) {|q, o| q.safe_order(o.first.to_s.underscore, o.last) }
rescue JSON::ParserError, Error::CustomError
context_error(context, code: "QUERY_ORDER_INVALID_VALUE")
end
private def query_limit(context, query, offset, limit)
return query if query.nil?
query = query.offset(offset) if offset&.positive?
query = query.limit(limit) if limit&.positive?
query
end
private def query_search(context, query, value)
return query if query.nil? || value.blank? || !query.respond_to?(:search)
query.search(value)
end
private def reduce_query(
context, query, offset: nil, limit: nil, order: nil, efilter: nil, search: nil, **_kargs
)
query = query_efilter(context, query, efilter)
query = query_search(context, query, search)
query = query_order(context, query, order)
query_limit(context, query, offset, limit)
end
private def parse_value(value)
case value
when String
JSON.parse(value)
when Hash
value
end
end
private def context_error(context, **kwargs)
BasicSchema.context_error(context, **kwargs)
end
end
#! /usr/bin/ruby
# frozen_string_literal: true
# NOTE: Thanks to Robert Mosolgo <rmosolgo@graphql.pro>
require "graphql"
module Types
class BaseObject < GraphQL::Schema::Object
end
end
class ListExtension < GraphQL::Schema::FieldExtension
def apply
object_type = field.type.unwrap
list_type_name = object_type.graphql_name
namespace = options[:namespace] || "default"
list_type =
Class.new(Types::BaseObject) do
self.graphql_name("#{namespace}_#{list_type_name}_list")
self.field(:rows, [object_type], null: true)
self.field(:totalCount, Integer, null: true)
end
field.type = list_type
field.argument :offset, Integer, required: false
field.argument :limit, Integer, required: false
field.argument :efilter, GraphQL::Types::JSON, required: false
field.argument :order, GraphQL::Types::JSON, required: false
field.argument :search, String, required: false
end
def after_resolve(value:, arguments:, **_kargs)
reduce_query(value, **arguments)
end
private def query_efilter(query, filter)
filter.present? ? query.efilter(filter) : query
end
private def query_order(query, order)
if order.present?
order.reduce(query) { |q, o| q.safe_order(o.first, o.last) }
else
query
end
end
private def query_paginate(query, offset, limit)
query.offset(offset || 0).limit(limit || 25)
end
private def reduce_query(
query, offset: nil, limit: nil, order: nil, efilter: nil, search: nil
)
query = query_efilter(query, efilter)
query = query_order(query, order)
query_paginate(query, offset, limit)
end
end
class PersonType < GraphQL::Schema::Object
field :id, ID, null: false
end
class QueryRoot < GraphQL::Schema::Object
field :people, PersonType, null: false, extensions: [ListExtension]
field :other_people, PersonType, null: false do
extension(ListExtension, namespace: "other")
end
end
class Schema < GraphQL::Schema
use GraphQL::Execution::Interpreter
use GraphQL::Analysis::AST
query QueryRoot
def initialize
super
@analysis_engine = GraphQL::Analysis::AST
end
end
puts Schema.to_definition
query_str = <<-GRAPHQL
{
people {
rows {
id
}
totalCount
}
}
GRAPHQL
data = {people: [{id: "1"}, {id: "2"}]}
pp Schema.execute(query_str, root_value: data).to_h
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment