Skip to content

Instantly share code, notes, and snippets.

Avatar

Ryan Foster theorygeek

  • Stripe, Inc.
  • Houston, TX
View GitHub Profile
@theorygeek
theorygeek / async_graph_resolution.rb
Last active Jun 28, 2019
Better GraphQL Promise Handling
View async_graph_resolution.rb
module AsyncGraphResolution
extend ActiveSupport::Concern
class_methods do
def async(method_name)
unbound_method = instance_method(method_name)
define_method(method_name) do |*args|
FiberResolver.new(unbound_method.bind(self), context, args, method_name, unbound_method.owner.name)
end
@theorygeek
theorygeek / graphql.rb
Created Mar 27, 2019
Guard Against Recursive Calls
View graphql.rb
GraphQL::Define::AssignObjectField.instance_eval do
alias original_call call
def call(*args, &block)
if !built_in_object(args[0]) && detect_proc_needed(args[2])
raise "You must wrap class-based types inside of a proc before attaching them to legacy-style types. Check #{args[0].name}.#{args[1]}"
end
original_call(*args, &block)
end
View error_instrumentation.rb
# frozen_string_literal: true
class ErrorInstrumentation
def instrument(type, field)
old_resolver = field.resolve_proc
new_resolver = -> (obj, args, ctx) do
begin # rubocop:disable Style/RedundantBegin
result = old_resolver.call(obj, args, ctx)
if result.is_a?(StandardError)
set_error_context(result, type, obj, field, args, ctx)
@theorygeek
theorygeek / initializers_graphql.rb
Created Sep 6, 2017
GraphQL Connection Counts
View initializers_graphql.rb
# Monkey Patch: Add a count field to all connections by default.
# Note, I tried to make this nicer, where it would just call the original method and then add a count field,
# but the challenge outlasted my patience. So I just replaced the whole method. TODO: better way to do this
module GraphQL
module Relay
module ConnectionType
def self.create_type(wrapped_type, edge_type: wrapped_type.edge_type, edge_class: GraphQL::Relay::Edge, nodes_field: ConnectionType.default_nodes_field, &block)
custom_edge_class = edge_class
# Any call that would trigger `wrapped_type.ensure_defined`
View caching_instrumentation.rb
# Define an instrumentation that performs the caching
class CachingInstrumentation
def instrument(_type, field)
return field unless field.metadata.include?(:cache_proc)
old_resolver = field.resolve_proc
new_resolver = -> (obj, args, ctx) do
# Get the caching key
cache_key = field.metadata[:cache_proc].call(obj, args, ctx)
@theorygeek
theorygeek / association_loader.rb
Last active Aug 28, 2020
Preloading Associations with graphql-batch
View association_loader.rb
# frozen_string_literal: true
class AssociationLoader < GraphQL::Batch::Loader
attr_reader :klass, :association
def initialize(klass, association)
raise ArgumentError, "association to load must be a symbol (got #{association.inspect})" unless association.is_a?(Symbol)
raise ArgumentError, "cannot load associations for class #{klass.name}" unless klass < ActiveRecord::Base
raise TypeError, "association #{association} does not exist on #{klass.name}" unless klass.reflect_on_association(association)
@klass = klass
@theorygeek
theorygeek / merged_query.rb
Created Nov 19, 2016
GraphQL Merged Queries
View merged_query.rb
module GraphQL
module Language
class MergedQuery
attr_accessor :original_queries, :original_variables
def initialize(queries, variables)
@original_queries = queries
@original_variables = variables
@fragment_counter = 0
end
@theorygeek
theorygeek / detect_bad_keys.rb
Created Jul 5, 2016
Finds keys in a hash that are not strings
View detect_bad_keys.rb
def detect_bad_keys(obj, parent_path)
result = []
case obj
when Hash
obj.each do |key, value|
if key.is_a?(String)
result.concat(detect_bad_keys(value, [*parent_path, key]))
else
result.push([*parent_path, key])
end
You can’t perform that action at this time.