# frozen_string_literal: true
require 'active_model'
require 'active_support'
require 'active_support/core_ext/module/attribute_accessors_per_thread'
require 'active_support/core_ext/string/strip'
require 'rails/graphql/version'
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'GraphiQL'
inflect.acronym 'GraphQL'
inflect.acronym 'GQLAst'
end
module Rails # :nodoc:
# = Rails GraphQL
#
# This implementation tries to be as close as the GraphQL spec as possible,
# meaning that this lib shares most of the same names and directions provided
# by the GraphQL spec. You can use {Rails::GraphQL::SPEC_VERSION}[rdoc-ref:Rails::GraphQL]
# to check which spec is being sued.
#
# Using ActiveSupport, define all the needed objects but doesn't load them
# since it's better to trust on eager_load in order to proper load the objects.
#
# A very important concept is that Singleton definitions are a direct
# connection to a {GraphQL Introspection}[http://spec.graphql.org/June2018/#sec-Introspection],
# meaning that to query the introspection is to query everything defined and
# associated with the GraphQL objects, the only exception are arguments,
# directives and fields:
#
# * Arguments: They are strictly associated with the object that
# defined it, also arguments with the same name doesn't mean they have the
# same behavior or configuration.
# * Directives: A directive definition belongs to the introspection
# and is handled in the Singleton extent. They are handled as instance
# whenever a definition or an execution uses them.
# * Fields: Many other types and helper containers holds a serie of
# fields, which means that fields with the same name will probably behave
# differently.
#
# TODO: Create the concept of MutationSet as a way to group mutations under
# the same class but placed onto a reference
module GraphQL
extend ActiveSupport::Autoload
include ActiveSupport::Configurable
# Stores the version of the GraphQL spec used for this implementation
SPEC_VERSION = 'June 2018'
# Runtime registry for request execution time
RuntimeRegistry = Class.new { thread_mattr_accessor :gql_runtime }
autoload :ToGQL
autoload :Helpers
autoload :Collectors
eager_autoload do
autoload_under :railties do
autoload :BaseGenerator
autoload :Controller
autoload :ControllerRuntime
autoload :LogSubscriber
end
autoload :Callback
autoload :Event
autoload :Native
autoload :Request
autoload :Source
autoload :TypeMap
autoload :Argument
autoload :Directive
autoload :Field
autoload :Introspection
autoload :Schema
autoload :Type
end
class << self
# Access to the type mapper
def type_map
@@type_map ||= GraphQL::TypeMap.new
end
# Find the key associated with the given +adapter_name+
def ar_adapter_key(adapter_name)
config.ar_adapters[adapter_name]
end
# This is a little helper to require ActiveRecord adapter specific
# configurations
def enable_ar_adapter(adapter_name)
return if (@@loaded_adapters ||= Set.new).include?(adapter_name)
path = "adapters/#{ar_adapter_key(adapter_name)}_adapter"
$LOAD_PATH.any? do |load_path|
next unless load_path.to_s =~ %r{\/app\/graphql$}
next unless File.exist?("#{load_path}/#{path}.rb")
load "#{load_path}/#{path}.rb"
end || load("#{__dir__}/graphql/#{path}.rb")
@@loaded_adapters << adapter_name
end
# Due to reloader process, adapter settings need to be reloaded
def reload_ar_adapters!
return unless defined?(@@loaded_adapters)
adapters, @@loaded_adapters = @@loaded_adapters, Set.new
adapters.map(&method(:enable_ar_adapter))
end
# Turn the given object into its string representation as GraphQl
# See {ToGQL}[rdoc-ref:Rails::GraphQL::ToGQL] class.
def to_gql(object, **xargs)
ToGQL.compile(object, **xargs)
end
alias to_graphql to_gql
# Returns a set instance with uniq directives from the given list.
# If the same directive class is given twice, it will raise an exception,
# since they must be uniq within a list of directives.
#
# Use the others argument to provide a list of already defined directives
# so the check can be performed using a +inherited_collection+.
#
# If a +source+ is provided, then an +:attach+ event will be triggered
# for each directive on the givem source element.
def directives_to_set(list, others = [], event = nil, **xargs)
others = others.dup
if (source = xargs.delete(:source)).present?
location = xargs.delete(:location) || source.try(:directive_location)
event ||= GraphQL::Event.new(:attach, source, phase: :definition, **xargs)
end
Array.wrap(list).each_with_object(Set.new) do |item, result|
raise ArgumentError, <<~MSG.squish unless item.kind_of?(GraphQL::Directive)
The "#{item.class}" is not a valid directive.
MSG
invalid = others.present? && (others.any? { |k| k.class.eql?(item.class) })
raise DuplicatedError, <<~MSG.squish if invalid
A @#{item.gql_name} directive have already been provided.
MSG
invalid_location = location.present? && !item.locations.include?(location)
raise ArgumentError, <<~MSG.squish if invalid_location
You cannot use @#{item.gql_name} directive due to location restriction.
MSG
unless event.nil?
item.assing_owner!(event.source)
event.trigger_object(item)
end
others << item
result << item
end
end
def eager_load! # :nodoc:
super
GraphQL::Request.eager_load!
GraphQL::Source.eager_load!
GraphQL::Directive.eager_load!
GraphQL::Type.eager_load!
end
end
end
end
require 'rails/graphql/config'
require 'rails/graphql/errors'
require 'rails/graphql/shortcuts'
require 'rails/graphql/railtie'