rails-graphql 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +31 -0
- data/ext/depend +3 -0
- data/ext/extconf.rb +57 -0
- data/ext/graphqlparser/Ast.cpp +346 -0
- data/ext/graphqlparser/Ast.h +1214 -0
- data/ext/graphqlparser/AstNode.h +36 -0
- data/ext/graphqlparser/AstVisitor.h +137 -0
- data/ext/graphqlparser/GraphQLParser.cpp +76 -0
- data/ext/graphqlparser/GraphQLParser.h +55 -0
- data/ext/graphqlparser/JsonVisitor.cpp +161 -0
- data/ext/graphqlparser/JsonVisitor.cpp.inc +456 -0
- data/ext/graphqlparser/JsonVisitor.h +121 -0
- data/ext/graphqlparser/JsonVisitor.h.inc +110 -0
- data/ext/graphqlparser/VERSION +1 -0
- data/ext/graphqlparser/c/GraphQLAst.cpp +324 -0
- data/ext/graphqlparser/c/GraphQLAst.h +180 -0
- data/ext/graphqlparser/c/GraphQLAstForEachConcreteType.h +44 -0
- data/ext/graphqlparser/c/GraphQLAstNode.cpp +25 -0
- data/ext/graphqlparser/c/GraphQLAstNode.h +33 -0
- data/ext/graphqlparser/c/GraphQLAstToJSON.cpp +21 -0
- data/ext/graphqlparser/c/GraphQLAstToJSON.h +24 -0
- data/ext/graphqlparser/c/GraphQLAstVisitor.cpp +55 -0
- data/ext/graphqlparser/c/GraphQLAstVisitor.h +53 -0
- data/ext/graphqlparser/c/GraphQLParser.cpp +35 -0
- data/ext/graphqlparser/c/GraphQLParser.h +54 -0
- data/ext/graphqlparser/dump_json_ast.cpp +48 -0
- data/ext/graphqlparser/lexer.lpp +324 -0
- data/ext/graphqlparser/parser.ypp +693 -0
- data/ext/graphqlparser/parsergen/lexer.cpp +2633 -0
- data/ext/graphqlparser/parsergen/lexer.h +528 -0
- data/ext/graphqlparser/parsergen/location.hh +189 -0
- data/ext/graphqlparser/parsergen/parser.tab.cpp +3300 -0
- data/ext/graphqlparser/parsergen/parser.tab.hpp +646 -0
- data/ext/graphqlparser/parsergen/position.hh +179 -0
- data/ext/graphqlparser/parsergen/stack.hh +156 -0
- data/ext/graphqlparser/syntaxdefs.h +19 -0
- data/ext/libgraphqlparser/AstNode.h +36 -0
- data/ext/libgraphqlparser/CMakeLists.txt +148 -0
- data/ext/libgraphqlparser/CONTRIBUTING.md +23 -0
- data/ext/libgraphqlparser/GraphQLParser.cpp +76 -0
- data/ext/libgraphqlparser/GraphQLParser.h +55 -0
- data/ext/libgraphqlparser/JsonVisitor.cpp +161 -0
- data/ext/libgraphqlparser/JsonVisitor.h +121 -0
- data/ext/libgraphqlparser/LICENSE +22 -0
- data/ext/libgraphqlparser/README.clang-tidy +7 -0
- data/ext/libgraphqlparser/README.md +84 -0
- data/ext/libgraphqlparser/ast/ast.ast +203 -0
- data/ext/libgraphqlparser/ast/ast.py +61 -0
- data/ext/libgraphqlparser/ast/c.py +100 -0
- data/ext/libgraphqlparser/ast/c.pyc +0 -0
- data/ext/libgraphqlparser/ast/c_impl.py +61 -0
- data/ext/libgraphqlparser/ast/c_impl.pyc +0 -0
- data/ext/libgraphqlparser/ast/c_visitor_impl.py +39 -0
- data/ext/libgraphqlparser/ast/c_visitor_impl.pyc +0 -0
- data/ext/libgraphqlparser/ast/casing.py +26 -0
- data/ext/libgraphqlparser/ast/casing.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx.py +197 -0
- data/ext/libgraphqlparser/ast/cxx.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx_impl.py +61 -0
- data/ext/libgraphqlparser/ast/cxx_impl.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx_json_visitor_header.py +42 -0
- data/ext/libgraphqlparser/ast/cxx_json_visitor_header.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.py +80 -0
- data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx_visitor.py +64 -0
- data/ext/libgraphqlparser/ast/cxx_visitor.pyc +0 -0
- data/ext/libgraphqlparser/ast/js.py +65 -0
- data/ext/libgraphqlparser/ast/license.py +10 -0
- data/ext/libgraphqlparser/ast/license.pyc +0 -0
- data/ext/libgraphqlparser/c/GraphQLAstNode.cpp +25 -0
- data/ext/libgraphqlparser/c/GraphQLAstNode.h +33 -0
- data/ext/libgraphqlparser/c/GraphQLAstToJSON.cpp +21 -0
- data/ext/libgraphqlparser/c/GraphQLAstToJSON.h +24 -0
- data/ext/libgraphqlparser/c/GraphQLAstVisitor.cpp +55 -0
- data/ext/libgraphqlparser/c/GraphQLAstVisitor.h +53 -0
- data/ext/libgraphqlparser/c/GraphQLParser.cpp +35 -0
- data/ext/libgraphqlparser/c/GraphQLParser.h +54 -0
- data/ext/libgraphqlparser/clang-tidy-all.sh +3 -0
- data/ext/libgraphqlparser/cmake/version.cmake +16 -0
- data/ext/libgraphqlparser/dump_json_ast.cpp +48 -0
- data/ext/libgraphqlparser/go/README.md +20 -0
- data/ext/libgraphqlparser/go/callbacks.go +18 -0
- data/ext/libgraphqlparser/go/gotest.go +64 -0
- data/ext/libgraphqlparser/lexer.lpp +324 -0
- data/ext/libgraphqlparser/libgraphqlparser.pc.in +11 -0
- data/ext/libgraphqlparser/parser.ypp +693 -0
- data/ext/libgraphqlparser/parsergen/lexer.cpp +2633 -0
- data/ext/libgraphqlparser/parsergen/lexer.h +528 -0
- data/ext/libgraphqlparser/parsergen/location.hh +189 -0
- data/ext/libgraphqlparser/parsergen/parser.tab.cpp +3300 -0
- data/ext/libgraphqlparser/parsergen/parser.tab.hpp +646 -0
- data/ext/libgraphqlparser/parsergen/position.hh +179 -0
- data/ext/libgraphqlparser/parsergen/stack.hh +156 -0
- data/ext/libgraphqlparser/python/CMakeLists.txt +14 -0
- data/ext/libgraphqlparser/python/README.md +5 -0
- data/ext/libgraphqlparser/python/example.py +31 -0
- data/ext/libgraphqlparser/syntaxdefs.h +19 -0
- data/ext/libgraphqlparser/test/BuildCAPI.c +5 -0
- data/ext/libgraphqlparser/test/CMakeLists.txt +25 -0
- data/ext/libgraphqlparser/test/JsonVisitorTests.cpp +28 -0
- data/ext/libgraphqlparser/test/ParserTests.cpp +352 -0
- data/ext/libgraphqlparser/test/kitchen-sink.graphql +59 -0
- data/ext/libgraphqlparser/test/kitchen-sink.json +1 -0
- data/ext/libgraphqlparser/test/schema-kitchen-sink.graphql +78 -0
- data/ext/libgraphqlparser/test/schema-kitchen-sink.json +1 -0
- data/ext/libgraphqlparser/test/valgrind.supp +33 -0
- data/ext/version.cpp +21 -0
- data/lib/generators/graphql/controller_generator.rb +22 -0
- data/lib/generators/graphql/schema_generator.rb +22 -0
- data/lib/generators/graphql/templates/controller.erb +5 -0
- data/lib/generators/graphql/templates/schema.erb +6 -0
- data/lib/graphqlparser.so +0 -0
- data/lib/rails-graphql.rb +2 -0
- data/lib/rails/graphql.rake +1 -0
- data/lib/rails/graphql.rb +185 -0
- data/lib/rails/graphql/adapters/mysql_adapter.rb +0 -0
- data/lib/rails/graphql/adapters/pg_adapter.rb +50 -0
- data/lib/rails/graphql/adapters/sqlite_adapter.rb +39 -0
- data/lib/rails/graphql/argument.rb +220 -0
- data/lib/rails/graphql/callback.rb +124 -0
- data/lib/rails/graphql/collectors.rb +14 -0
- data/lib/rails/graphql/collectors/hash_collector.rb +83 -0
- data/lib/rails/graphql/collectors/idented_collector.rb +73 -0
- data/lib/rails/graphql/collectors/json_collector.rb +114 -0
- data/lib/rails/graphql/config.rb +61 -0
- data/lib/rails/graphql/directive.rb +203 -0
- data/lib/rails/graphql/directive/deprecated_directive.rb +59 -0
- data/lib/rails/graphql/directive/include_directive.rb +24 -0
- data/lib/rails/graphql/directive/skip_directive.rb +24 -0
- data/lib/rails/graphql/errors.rb +42 -0
- data/lib/rails/graphql/event.rb +141 -0
- data/lib/rails/graphql/field.rb +318 -0
- data/lib/rails/graphql/field/input_field.rb +92 -0
- data/lib/rails/graphql/field/mutation_field.rb +52 -0
- data/lib/rails/graphql/field/output_field.rb +96 -0
- data/lib/rails/graphql/field/proxied_field.rb +131 -0
- data/lib/rails/graphql/field/resolved_field.rb +96 -0
- data/lib/rails/graphql/field/scoped_config.rb +22 -0
- data/lib/rails/graphql/field/typed_field.rb +104 -0
- data/lib/rails/graphql/helpers.rb +40 -0
- data/lib/rails/graphql/helpers/attribute_delegator.rb +39 -0
- data/lib/rails/graphql/helpers/inherited_collection.rb +152 -0
- data/lib/rails/graphql/helpers/leaf_from_ar.rb +141 -0
- data/lib/rails/graphql/helpers/registerable.rb +103 -0
- data/lib/rails/graphql/helpers/with_arguments.rb +125 -0
- data/lib/rails/graphql/helpers/with_assignment.rb +113 -0
- data/lib/rails/graphql/helpers/with_callbacks.rb +55 -0
- data/lib/rails/graphql/helpers/with_directives.rb +126 -0
- data/lib/rails/graphql/helpers/with_events.rb +81 -0
- data/lib/rails/graphql/helpers/with_fields.rb +141 -0
- data/lib/rails/graphql/helpers/with_namespace.rb +40 -0
- data/lib/rails/graphql/helpers/with_owner.rb +35 -0
- data/lib/rails/graphql/helpers/with_schema_fields.rb +230 -0
- data/lib/rails/graphql/helpers/with_validator.rb +52 -0
- data/lib/rails/graphql/introspection.rb +53 -0
- data/lib/rails/graphql/native.rb +56 -0
- data/lib/rails/graphql/native/functions.rb +38 -0
- data/lib/rails/graphql/native/location.rb +41 -0
- data/lib/rails/graphql/native/pointers.rb +23 -0
- data/lib/rails/graphql/native/visitor.rb +349 -0
- data/lib/rails/graphql/railtie.rb +85 -0
- data/lib/rails/graphql/railties/base_generator.rb +35 -0
- data/lib/rails/graphql/railties/controller.rb +101 -0
- data/lib/rails/graphql/railties/controller_runtime.rb +40 -0
- data/lib/rails/graphql/railties/log_subscriber.rb +62 -0
- data/lib/rails/graphql/request.rb +343 -0
- data/lib/rails/graphql/request/arguments.rb +93 -0
- data/lib/rails/graphql/request/component.rb +100 -0
- data/lib/rails/graphql/request/component/field.rb +225 -0
- data/lib/rails/graphql/request/component/fragment.rb +118 -0
- data/lib/rails/graphql/request/component/operation.rb +178 -0
- data/lib/rails/graphql/request/component/operation/subscription.rb +16 -0
- data/lib/rails/graphql/request/component/spread.rb +119 -0
- data/lib/rails/graphql/request/component/typename.rb +82 -0
- data/lib/rails/graphql/request/context.rb +51 -0
- data/lib/rails/graphql/request/errors.rb +54 -0
- data/lib/rails/graphql/request/event.rb +112 -0
- data/lib/rails/graphql/request/helpers/directives.rb +64 -0
- data/lib/rails/graphql/request/helpers/selection_set.rb +87 -0
- data/lib/rails/graphql/request/helpers/value_writers.rb +115 -0
- data/lib/rails/graphql/request/steps/organizable.rb +146 -0
- data/lib/rails/graphql/request/steps/prepareable.rb +33 -0
- data/lib/rails/graphql/request/steps/resolveable.rb +32 -0
- data/lib/rails/graphql/request/strategy.rb +249 -0
- data/lib/rails/graphql/request/strategy/dynamic_instance.rb +41 -0
- data/lib/rails/graphql/request/strategy/multi_query_strategy.rb +36 -0
- data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +28 -0
- data/lib/rails/graphql/schema.rb +272 -0
- data/lib/rails/graphql/shortcuts.rb +77 -0
- data/lib/rails/graphql/source.rb +371 -0
- data/lib/rails/graphql/source/active_record/builders.rb +154 -0
- data/lib/rails/graphql/source/active_record_source.rb +231 -0
- data/lib/rails/graphql/source/scoped_arguments.rb +87 -0
- data/lib/rails/graphql/to_gql.rb +368 -0
- data/lib/rails/graphql/type.rb +138 -0
- data/lib/rails/graphql/type/enum.rb +206 -0
- data/lib/rails/graphql/type/enum/directive_location_enum.rb +30 -0
- data/lib/rails/graphql/type/enum/type_kind_enum.rb +57 -0
- data/lib/rails/graphql/type/input.rb +134 -0
- data/lib/rails/graphql/type/interface.rb +82 -0
- data/lib/rails/graphql/type/object.rb +111 -0
- data/lib/rails/graphql/type/object/directive_object.rb +34 -0
- data/lib/rails/graphql/type/object/enum_value_object.rb +25 -0
- data/lib/rails/graphql/type/object/field_object.rb +54 -0
- data/lib/rails/graphql/type/object/input_value_object.rb +49 -0
- data/lib/rails/graphql/type/object/schema_object.rb +40 -0
- data/lib/rails/graphql/type/object/type_object.rb +136 -0
- data/lib/rails/graphql/type/scalar.rb +71 -0
- data/lib/rails/graphql/type/scalar/bigint_scalar.rb +34 -0
- data/lib/rails/graphql/type/scalar/binary_scalar.rb +30 -0
- data/lib/rails/graphql/type/scalar/boolean_scalar.rb +37 -0
- data/lib/rails/graphql/type/scalar/date_scalar.rb +34 -0
- data/lib/rails/graphql/type/scalar/date_time_scalar.rb +32 -0
- data/lib/rails/graphql/type/scalar/decimal_scalar.rb +35 -0
- data/lib/rails/graphql/type/scalar/float_scalar.rb +32 -0
- data/lib/rails/graphql/type/scalar/id_scalar.rb +39 -0
- data/lib/rails/graphql/type/scalar/int_scalar.rb +36 -0
- data/lib/rails/graphql/type/scalar/string_scalar.rb +28 -0
- data/lib/rails/graphql/type/scalar/time_scalar.rb +40 -0
- data/lib/rails/graphql/type/union.rb +87 -0
- data/lib/rails/graphql/type_map.rb +347 -0
- data/lib/rails/graphql/version.rb +7 -0
- data/test/assets/introspection-db.json +0 -0
- data/test/assets/introspection-mem.txt +1 -0
- data/test/assets/introspection.gql +91 -0
- data/test/assets/luke.jpg +0 -0
- data/test/assets/mem.gql +428 -0
- data/test/assets/sqlite.gql +423 -0
- data/test/config.rb +80 -0
- data/test/graphql/request/context_test.rb +70 -0
- data/test/graphql/schema_test.rb +190 -0
- data/test/graphql/source_test.rb +237 -0
- data/test/graphql/type/enum_test.rb +203 -0
- data/test/graphql/type/input_test.rb +138 -0
- data/test/graphql/type/interface_test.rb +72 -0
- data/test/graphql/type/object_test.rb +104 -0
- data/test/graphql/type/scalar/bigint_scalar_test.rb +42 -0
- data/test/graphql/type/scalar/binary_scalar_test.rb +17 -0
- data/test/graphql/type/scalar/boolean_scalar_test.rb +40 -0
- data/test/graphql/type/scalar/date_scalar_test.rb +29 -0
- data/test/graphql/type/scalar/date_time_scalar_test.rb +29 -0
- data/test/graphql/type/scalar/decimal_scalar_test.rb +28 -0
- data/test/graphql/type/scalar/float_scalar_test.rb +22 -0
- data/test/graphql/type/scalar/id_scalar_test.rb +26 -0
- data/test/graphql/type/scalar/int_scalar_test.rb +26 -0
- data/test/graphql/type/scalar/string_scalar_test.rb +17 -0
- data/test/graphql/type/scalar/time_scalar_test.rb +36 -0
- data/test/graphql/type/scalar_test.rb +45 -0
- data/test/graphql/type/union_test.rb +82 -0
- data/test/graphql/type_map_test.rb +362 -0
- data/test/graphql/type_test.rb +68 -0
- data/test/graphql_test.rb +55 -0
- data/test/integration/config.rb +56 -0
- data/test/integration/memory/star_wars_introspection_test.rb +144 -0
- data/test/integration/memory/star_wars_query_test.rb +184 -0
- data/test/integration/memory/star_wars_validation_test.rb +99 -0
- data/test/integration/schemas/memory.rb +232 -0
- data/test/integration/schemas/sqlite.rb +82 -0
- data/test/integration/sqlite/star_wars_introspection_test.rb +15 -0
- data/test/integration/sqlite/star_wars_mutation_test.rb +82 -0
- data/test/integration/sqlite/star_wars_query_test.rb +71 -0
- data/test/test_ext.rb +48 -0
- metadata +509 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
class Request # :nodoc:
|
6
|
+
# Helper methods for the prepare step of a request
|
7
|
+
module Prepareable
|
8
|
+
# Prepare the object
|
9
|
+
def prepare!
|
10
|
+
capture_exception(:prepare) { prepare }
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
# Normal mode of the prepare step
|
16
|
+
def prepare
|
17
|
+
return if invalid?
|
18
|
+
prepare_then { prepare_fields }
|
19
|
+
end
|
20
|
+
|
21
|
+
# The actual process that prepare the object
|
22
|
+
def prepare_then(after_block = nil, &block)
|
23
|
+
return if invalid?
|
24
|
+
stacked do
|
25
|
+
block.call if block.present?
|
26
|
+
trigger_event(:prepared)
|
27
|
+
after_block.call if after_block.present?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
class Request # :nodoc:
|
6
|
+
# Helper methods for the resolve step of a request
|
7
|
+
module Resolveable
|
8
|
+
# Resolve the object
|
9
|
+
def resolve!
|
10
|
+
capture_exception(:resolve) { resolve }
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
# Normal mode of the resolve step
|
16
|
+
def resolve
|
17
|
+
invalid? ? try(:resolve_invalid) : resolve_then
|
18
|
+
end
|
19
|
+
|
20
|
+
# The actual process that resolve the object
|
21
|
+
def resolve_then(after_block = nil, &block)
|
22
|
+
return if invalid?
|
23
|
+
stacked do
|
24
|
+
block.call if block.present?
|
25
|
+
trigger_event(:finalize)
|
26
|
+
after_block.call if after_block.present?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
class Request # :nodoc:
|
6
|
+
# = GraphQL Request Strategy
|
7
|
+
#
|
8
|
+
# This is the base class for the strategies of resolving a request.
|
9
|
+
class Strategy
|
10
|
+
extend ActiveSupport::Autoload
|
11
|
+
|
12
|
+
autoload :DynamicInstance
|
13
|
+
|
14
|
+
eager_autoload do
|
15
|
+
autoload :SequencedStrategy
|
16
|
+
autoload :MultiQueryStrategy
|
17
|
+
end
|
18
|
+
|
19
|
+
# Configurations for the prepare step
|
20
|
+
PREPARE_XARGS = { object?: true, reverse?: true }.freeze
|
21
|
+
|
22
|
+
# The priority of the strategy
|
23
|
+
class_attribute :priority, instance_accessor: false, default: 1
|
24
|
+
|
25
|
+
delegate :operations, :errors, :response, :schema, to: :request
|
26
|
+
|
27
|
+
attr_reader :listeners, :request, :context
|
28
|
+
|
29
|
+
class << self
|
30
|
+
# Check if the strategy can resolve the given +request+. By default,
|
31
|
+
# strategies cannot resolve a request. Override this method with a
|
32
|
+
# valid checker.
|
33
|
+
def can_resolve?(_)
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(request)
|
39
|
+
@request = request
|
40
|
+
collect_request_listeners
|
41
|
+
end
|
42
|
+
|
43
|
+
# Executes the strategy in the normal mode
|
44
|
+
def resolve!
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
# Find a given +type+ and store it on request cache
|
49
|
+
def find_type!(type)
|
50
|
+
request.cache(:types)[type] ||= schema.find_type!(type)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Find a given +directive+ and store it on request cache
|
54
|
+
def find_directive!(directive)
|
55
|
+
request.cache(:directives)[directive] ||= schema.find_directive!(directive)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Check if it's enabled to collect listeners
|
59
|
+
def add_listeners?
|
60
|
+
!listeners.frozen?
|
61
|
+
end
|
62
|
+
|
63
|
+
# Check if any listener were actually added
|
64
|
+
def listeners?
|
65
|
+
listeners.any?
|
66
|
+
end
|
67
|
+
|
68
|
+
# Check if any object is listening to a given +event_name+
|
69
|
+
def listening_to?(event_name)
|
70
|
+
listeners? && listeners.key?(event_name.to_sym)
|
71
|
+
end
|
72
|
+
|
73
|
+
# When running an stacked operation, make sure that the object was added
|
74
|
+
# to the list of the listeners
|
75
|
+
def stacked(object, &block)
|
76
|
+
request.stacked(object, &block)
|
77
|
+
end
|
78
|
+
|
79
|
+
# When a +field+ has a perform step, run it under the context of the
|
80
|
+
# prepared value from the data pool
|
81
|
+
def perform(field)
|
82
|
+
context.stacked(@data_pool[field]) do
|
83
|
+
safe_store_data(field) do
|
84
|
+
Event.trigger(:perform, field, self, &field.performer)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Execute the prepare step for the given +field+ and execute the given
|
90
|
+
# block using context stack
|
91
|
+
def prepare(field, &block)
|
92
|
+
value = safe_store_data(field) do
|
93
|
+
Event.trigger(:prepare, field, self, **PREPARE_XARGS)
|
94
|
+
end
|
95
|
+
|
96
|
+
return context.stacked(value, &block) unless value.nil?
|
97
|
+
|
98
|
+
stack = field.all_events[:prepare].map { |cb| cb.source_location.join(':') }
|
99
|
+
request.report_node_error(<<~MSG.squish, field, stack: stack.reverse)
|
100
|
+
It is expected to get a result from prepare events
|
101
|
+
MSG
|
102
|
+
end
|
103
|
+
|
104
|
+
# Resolve a value for a given object, It uses the +args+ to prevent
|
105
|
+
# problems with nil values.
|
106
|
+
def resolve(field, *args, array: false, decorate: false, &block)
|
107
|
+
rescue_with_handler(field: field) do
|
108
|
+
prepared = data_for(args, field)&.last
|
109
|
+
args << Event.trigger(:resolve, field, self, prepared: prepared,
|
110
|
+
&field.resolver) if field.try(:dynamic_resolver?)
|
111
|
+
end if args.size.zero?
|
112
|
+
|
113
|
+
# Now we have a value to set on the context
|
114
|
+
value = args.last
|
115
|
+
value = field.decorate(value) if decorate
|
116
|
+
context.stacked(value) do |current|
|
117
|
+
if !array
|
118
|
+
block.call(current)
|
119
|
+
field.write_value(current)
|
120
|
+
else
|
121
|
+
field.write_array(current, &block)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Safe trigger an event and ensure to send any exception to the request
|
127
|
+
# handler
|
128
|
+
def rescue_with_handler(**extra)
|
129
|
+
yield
|
130
|
+
rescue => error
|
131
|
+
request.rescue_with_handler(error, **extra)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Check if the given class is in the pool, or add a new instance to the
|
135
|
+
# pool, and then set the instance as the current object
|
136
|
+
def instance_for(klass)
|
137
|
+
@objects_pool[klass] ||= begin
|
138
|
+
@objects_pool.each_value.find do |value|
|
139
|
+
value.is_a?(klass)
|
140
|
+
end || begin
|
141
|
+
instance = klass.new
|
142
|
+
instance = DynamicInstance.new(instance) unless klass < GraphQL::Schema ||
|
143
|
+
klass < GraphQL::Type::Object
|
144
|
+
instance
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Trigger an event using a set of filtered objects from +request.stack+.
|
150
|
+
# {+trigger_all+}[rdoc-ref:Rails::GraphQL::Event#trigger_all].
|
151
|
+
# The filter is based on the listeners that were collected by the
|
152
|
+
# strategy.
|
153
|
+
def trigger_event(event_name, **xargs)
|
154
|
+
return unless listening_to?(event_name)
|
155
|
+
|
156
|
+
objects = listeners[event_name.to_sym] & request.stack
|
157
|
+
return if objects.empty?
|
158
|
+
|
159
|
+
Event.trigger(event_name, objects, self, **xargs)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Check what kind of event listeners the object have, in order to speed
|
163
|
+
# up processing by avoiding unnecesary event instances
|
164
|
+
def add_listener(object)
|
165
|
+
return unless add_listeners?
|
166
|
+
|
167
|
+
object.all_listeners.each do |event_name|
|
168
|
+
listeners[event_name] << object
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Store a given resolve +value+ for a given +field+
|
173
|
+
def store_data(field, value)
|
174
|
+
@data_pool[field] = value
|
175
|
+
end
|
176
|
+
|
177
|
+
# Only store a given +value+ for a given +field+ if it is not set yet
|
178
|
+
def safe_store_data(field, value = nil)
|
179
|
+
rescue_with_handler(field: field) do
|
180
|
+
value ||= yield if block_given?
|
181
|
+
@data_pool[field] ||= value unless value.nil?
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
protected
|
186
|
+
|
187
|
+
# Clean and enable the collecting of listeners
|
188
|
+
def release_listeners!
|
189
|
+
@listeners = @base_listeners.dup
|
190
|
+
end
|
191
|
+
|
192
|
+
# Disable the collecting of listeners
|
193
|
+
def lock_listeners!
|
194
|
+
listeners.freeze
|
195
|
+
end
|
196
|
+
|
197
|
+
# A shortcut for +release_listeners!+ and +lock_listeners!+
|
198
|
+
def collect_listeners
|
199
|
+
release_listeners!
|
200
|
+
yield
|
201
|
+
lock_listeners!
|
202
|
+
end
|
203
|
+
|
204
|
+
# This is where the strategy is most effective. By preparing the tree,
|
205
|
+
# it can load data in a pretty smart way
|
206
|
+
def collect_data
|
207
|
+
@data_pool = {}
|
208
|
+
@objects_pool = {}
|
209
|
+
@context = request.build(Request::Context)
|
210
|
+
|
211
|
+
# TODO: Create an orchestrator to allow cross query loading
|
212
|
+
yield if listening_to?(:prepare)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Initiate the response context, named
|
216
|
+
# ({Request::Context}[rdoc-ref:Rails::GraphQL::Request::Context])
|
217
|
+
# and start collecting results
|
218
|
+
def collect_response
|
219
|
+
yield
|
220
|
+
ensure
|
221
|
+
@context = @objects_pool = @data_pool = @listeners = nil
|
222
|
+
end
|
223
|
+
|
224
|
+
# Fetch the data for a given field and set as the first element
|
225
|
+
# of the returned list
|
226
|
+
def data_for(result, field)
|
227
|
+
return result << @data_pool[field] if @data_pool.key?(field)
|
228
|
+
return if field.entry_point?
|
229
|
+
|
230
|
+
current, key = context.current_value, field.method_name
|
231
|
+
return result << current.public_send(key) if current.respond_to?(key)
|
232
|
+
|
233
|
+
result << current[key] if current.respond_to?(:key?) && current.key?(key)
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
|
238
|
+
# Collect the base listeners from the request
|
239
|
+
def collect_request_listeners
|
240
|
+
@listeners = Hash.new { |h, k| h[k] = [] }
|
241
|
+
add_listener(request)
|
242
|
+
|
243
|
+
lock_listeners!
|
244
|
+
@base_listeners = @listeners
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
class Request # :nodoc:
|
6
|
+
# = GraphQl Strategy Dynamic Instance
|
7
|
+
#
|
8
|
+
# When an event is call on non-object types, this class allows both
|
9
|
+
# finding a method on two different places, the interface or union
|
10
|
+
# definition, or on the currect object type-class.
|
11
|
+
class Strategy::DynamicInstance < Helpers::AttributeDelegator
|
12
|
+
def instance_variable_set(ivar, value)
|
13
|
+
__getobj__.instance_variable_set(ivar, value)
|
14
|
+
__current_object__&.instance_variable_set(ivar, value)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def respond_to_missing?(method_name, include_private = false) # :nodoc:
|
20
|
+
__current_object__&.respond_to?(method_name, include_private) || super
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(method_name, *args, **xargs, &block) # :nodoc:
|
24
|
+
object = __current_object__
|
25
|
+
|
26
|
+
return super unless object&.respond_to?(method_name)
|
27
|
+
object.public_send(method_name, *args, **xargs, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def __current_object__ # :nodoc:
|
31
|
+
return unless __getobj__.instance_variable_defined?(:@event)
|
32
|
+
|
33
|
+
event = __getobj__.instance_variable_get(:@event)
|
34
|
+
return if event.nil? || (object = event.source.try(:current_object)).nil?
|
35
|
+
|
36
|
+
event.strategy.instance_for(object)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
class Request # :nodoc:
|
6
|
+
# = GraphQl Multi Query Strategy
|
7
|
+
#
|
8
|
+
# This is a resolution strategy to solve requests that only contain
|
9
|
+
# queries, allowing the strategy to collect all the information for all
|
10
|
+
# the queries in a single step before resolving it.
|
11
|
+
class Strategy::MultiQueryStrategy < Strategy
|
12
|
+
self.priority = 10
|
13
|
+
|
14
|
+
def self.can_resolve?(request) # :nodoc:
|
15
|
+
request.operations.values.all?(&:query?)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Executes the strategy in the normal mode
|
19
|
+
def resolve!
|
20
|
+
response.with_stack(:data) do
|
21
|
+
for_each_operation { |op| collect_listeners { op.organize! } }
|
22
|
+
for_each_operation { |op| collect_data { op.prepare! } }
|
23
|
+
for_each_operation { |op| collect_response { op.resolve! } }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Execute a given block for each defined operation
|
30
|
+
def for_each_operation
|
31
|
+
operations.each_value { |op| yield op }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
class Request # :nodoc:
|
6
|
+
# = GraphQl Sequenced Strategy
|
7
|
+
#
|
8
|
+
# This is the default resolution strategy, where each operation is
|
9
|
+
# performed in sequece, and they don't relate to each other in any way.
|
10
|
+
class Strategy::SequencedStrategy < Strategy
|
11
|
+
def self.can_resolve?(_) # :nodoc:
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
# Executes the strategy in the normal mode
|
16
|
+
def resolve!
|
17
|
+
response.with_stack(:data) do
|
18
|
+
operations.each_value do |op|
|
19
|
+
collect_listeners { op.organize! }
|
20
|
+
collect_data { op.prepare! }
|
21
|
+
collect_response { op.resolve! }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
# = GraphQL Schema
|
6
|
+
#
|
7
|
+
# This is a pure representation of a GraphQL schema.
|
8
|
+
# See: http://spec.graphql.org/June2018/#SchemaDefinition
|
9
|
+
#
|
10
|
+
# In addition to the spec implementation, this also allows separation by
|
11
|
+
# namespaces, where each schema is associated with one and only one
|
12
|
+
# namespace, guiding requests and types searching.
|
13
|
+
#
|
14
|
+
# This class works similary to the {TypeMap}[rdoc-ref:Rails::base_classMap]
|
15
|
+
# class, where its purpose is to know which QueryFields, Mutations, and
|
16
|
+
# Subscriptions are available. The main difference is that it doesn't hold
|
17
|
+
# namespace-based objects, since each schema is associated to a single
|
18
|
+
# namespace.
|
19
|
+
class Schema
|
20
|
+
extend Helpers::WithSchemaFields
|
21
|
+
extend Helpers::WithDirectives
|
22
|
+
extend Helpers::Registerable
|
23
|
+
extend GraphQL::Introspection
|
24
|
+
|
25
|
+
include ActiveSupport::Configurable
|
26
|
+
include ActiveSupport::Rescuable
|
27
|
+
|
28
|
+
# The purpose of instantiating an schema is to have access to its
|
29
|
+
# public methods. It then runs from the strategy perspective, pointing
|
30
|
+
# out any other methods to the manually set event
|
31
|
+
delegate_missing_to :event
|
32
|
+
attr_reader :event
|
33
|
+
|
34
|
+
self.abstract = true
|
35
|
+
self.spec_object = true
|
36
|
+
self.directive_location = :schema
|
37
|
+
|
38
|
+
# Imports schema specific configurations
|
39
|
+
configure do |config|
|
40
|
+
%i[enable_string_collector request_strategies].each do |name|
|
41
|
+
config.send("#{name}=", GraphQL.config.send(name))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class << self
|
46
|
+
delegate :type_map, :logger, to: '::Rails::GraphQL'
|
47
|
+
|
48
|
+
# Mark the given class to be pending of registration
|
49
|
+
def inherited(subclass)
|
50
|
+
subclass.spec_object = false
|
51
|
+
subclass.abstract = false
|
52
|
+
super if defined? super
|
53
|
+
end
|
54
|
+
|
55
|
+
# :singleton-method:
|
56
|
+
# Since there are only one schema per namespace, the name is constant
|
57
|
+
def gql_name
|
58
|
+
'schema'
|
59
|
+
end
|
60
|
+
|
61
|
+
alias graphql_name gql_name
|
62
|
+
|
63
|
+
# :singleton-method:
|
64
|
+
# Since there is only one schema per namespace, then both kind and
|
65
|
+
# to_sym, which is used to register, are the same
|
66
|
+
def kind # :nodoc:
|
67
|
+
:schema
|
68
|
+
end
|
69
|
+
|
70
|
+
alias to_sym kind
|
71
|
+
|
72
|
+
# :singleton-method:
|
73
|
+
# Use a soft mode to find a schema associated with a namespace
|
74
|
+
def find(namespace)
|
75
|
+
type_map.fetch(:schema,
|
76
|
+
namespaces: namespace,
|
77
|
+
base_class: :Schema,
|
78
|
+
exclusive: true,
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
# :singleton-method:
|
83
|
+
# Find the schema associated to the given namespace
|
84
|
+
def find!(namespace)
|
85
|
+
type_map.fetch!(:schema,
|
86
|
+
namespaces: namespace,
|
87
|
+
base_class: :Schema,
|
88
|
+
exclusive: true,
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
# :singleton-method:
|
93
|
+
# For campatibility with type map
|
94
|
+
def eager_load!
|
95
|
+
TypeMap.loaded! :Schema
|
96
|
+
end
|
97
|
+
|
98
|
+
# Find all types that are available for the current schema
|
99
|
+
def types(base_class: :Type, &block)
|
100
|
+
type_map.each_from(namespace, base_class: base_class, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Schemas are assigned to a single namespace
|
104
|
+
def set_namespace(*list)
|
105
|
+
super(list.first)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Schemas are assigned to a single namespace and not inherited
|
109
|
+
def namespace(*list)
|
110
|
+
list.blank? ? (namespaces.first || :base) : set_namespace(*list)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Check if the class is already registered in the typemap
|
114
|
+
def registered?
|
115
|
+
type_map.object_exist?(self, exclusive: true)
|
116
|
+
end
|
117
|
+
|
118
|
+
# The process to register a class and it's name on the index
|
119
|
+
def register!
|
120
|
+
return if self == GraphQL::Schema
|
121
|
+
|
122
|
+
unless registered?
|
123
|
+
super if defined? super
|
124
|
+
return type_map.register(self).method(:validate!)
|
125
|
+
end
|
126
|
+
|
127
|
+
current = type_map.fetch(:schema,
|
128
|
+
namespaces: namespace,
|
129
|
+
base_class: :Schema,
|
130
|
+
exclusive: true,
|
131
|
+
)
|
132
|
+
|
133
|
+
raise ArgumentError, <<~MSG.squish
|
134
|
+
The #{namespace.inspect} namespace is already assigned to "#{current.name}".
|
135
|
+
Please change the namespace for "#{klass.name}" class.
|
136
|
+
MSG
|
137
|
+
end
|
138
|
+
|
139
|
+
# Checks if a given method can act as resolver
|
140
|
+
def gql_resolver?(method_name)
|
141
|
+
(instance_methods - GraphQL::Schema.instance_methods).include?(method_name)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Find a given +type+ associated with the schema
|
145
|
+
def find_type(type, **xargs)
|
146
|
+
xargs[:base_class] = :Type
|
147
|
+
xargs[:namespaces] = namespaces
|
148
|
+
type_map.fetch(type, **xargs)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Find a given +type+ associated with the schema. It will raise an
|
152
|
+
# exception if the +type+ can not be found
|
153
|
+
def find_type!(type, **xargs)
|
154
|
+
xargs[:base_class] = :Type
|
155
|
+
xargs[:namespaces] = namespaces
|
156
|
+
type_map.fetch!(type, **xargs)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Find a given +directive+ associated with the schema. It will raise an
|
160
|
+
# exception if the +directive+ can not be found
|
161
|
+
def find_directive!(directive, **xargs)
|
162
|
+
xargs[:base_class] = :Directive
|
163
|
+
xargs[:namespaces] = namespaces
|
164
|
+
type_map.fetch!(directive, **xargs)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Describe a schema as a GraphQL string
|
168
|
+
def to_gql(**xargs)
|
169
|
+
ToGQL.describe(self, **xargs)
|
170
|
+
end
|
171
|
+
|
172
|
+
protected
|
173
|
+
|
174
|
+
# TODO: Maybe provide an optional 'Any' scalar
|
175
|
+
|
176
|
+
# Generate the helper methods to easily create types within the
|
177
|
+
# definition of the schema
|
178
|
+
GraphQL::Type::KINDS.each do |kind|
|
179
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
180
|
+
def #{kind.underscore}(name, **xargs, &block)
|
181
|
+
create_type(name, :#{kind}, **xargs, &block)
|
182
|
+
end
|
183
|
+
RUBY
|
184
|
+
end
|
185
|
+
|
186
|
+
# Helper method to create a single source
|
187
|
+
def source(object, superclass = nil, **xargs, &block)
|
188
|
+
superclass ||= GraphQL::Source.find_for!(object)
|
189
|
+
|
190
|
+
xargs[:suffix] = 'Source'
|
191
|
+
schema_namespace = namespace
|
192
|
+
create_klass(object, superclass, GraphQL::Source, **xargs) do
|
193
|
+
set_namespace schema_namespace
|
194
|
+
|
195
|
+
xargs.each do |key, value|
|
196
|
+
_, segment = key.to_s.split('skip_on_')
|
197
|
+
skip_on segment, value if segment.present?
|
198
|
+
end
|
199
|
+
|
200
|
+
instance_exec(&block) if block.present?
|
201
|
+
build!
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Helper method to create multiple sources with the same type
|
206
|
+
def sources(*list, of_type: nil, &block)
|
207
|
+
list = list.flatten
|
208
|
+
|
209
|
+
of_type ||= GraphQL::Source.find_for!(list.first)
|
210
|
+
list.each { |object| source(object, of_type, &block) }
|
211
|
+
end
|
212
|
+
|
213
|
+
# A simpler way to create a new type object without having to create
|
214
|
+
# a class in a different file
|
215
|
+
def create_type(name, superclass, **xargs, &block)
|
216
|
+
superclass = GraphQL::Type.const_get(superclass) unless superclass.is_a?(Module)
|
217
|
+
xargs[:suffix] ||= superclass.base_type.name.demodulize
|
218
|
+
|
219
|
+
create_klass(name, superclass, GraphQL::Type, **xargs, &block)
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
# Helper to create objects that are actually classes of a given
|
225
|
+
# +superclass+ ensuring that it inherits from +base_class+.
|
226
|
+
#
|
227
|
+
# The +suffix+ option can ensures that the name of the created
|
228
|
+
# class ends with a specific suffix.
|
229
|
+
def create_klass(name_or_object, superclass, base_class = nil, **xargs, &block)
|
230
|
+
name = name_or_object.is_a?(Module) ? name_or_object.name : name_or_object.to_s
|
231
|
+
|
232
|
+
base_module = name.classify.deconstantize
|
233
|
+
base_module.prepend('GraphQL::') unless base_module =~ /^GraphQL(::|$)/
|
234
|
+
base_module = base_module.delete_suffix('::').constantize
|
235
|
+
|
236
|
+
klass_name = name.classify.demodulize
|
237
|
+
klass_name += xargs[:suffix] if xargs.key?(:suffix) &&
|
238
|
+
!klass_name.ends_with?(xargs[:suffix])
|
239
|
+
|
240
|
+
if base_module.const_defined?(klass_name)
|
241
|
+
klass = base_module.const_get(klass_name)
|
242
|
+
|
243
|
+
raise DuplicatedError, <<~MSG.squish unless !xargs[:once] && klass < superclass
|
244
|
+
A constant named "#{klass_name}" already exists for the
|
245
|
+
"#{base_module.name}" module.
|
246
|
+
MSG
|
247
|
+
else
|
248
|
+
base_class ||= superclass.ancestors.find { |k| k.superclass === Class }
|
249
|
+
|
250
|
+
valid = superclass.is_a?(Module) && superclass < base_class
|
251
|
+
raise DefinitionError, <<~MSG.squish unless valid
|
252
|
+
The given "#{superclass}" superclass does not inherites from
|
253
|
+
#{base_class.name} class.
|
254
|
+
MSG
|
255
|
+
|
256
|
+
klass = base_module.const_set(klass_name, Class.new(superclass))
|
257
|
+
end
|
258
|
+
|
259
|
+
klass.abstract = xargs[:abstract] if xargs.key?(:abstract)
|
260
|
+
klass.assigned_to = name_or_object if name_or_object.is_a?(Module) &&
|
261
|
+
klass.is_a?(Helpers::WithAssignment)
|
262
|
+
|
263
|
+
klass.set_namespace(namespace)
|
264
|
+
klass.instance_exec(&block) if block.present?
|
265
|
+
klass
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
ActiveSupport.run_load_hooks(:graphql, Schema)
|
271
|
+
end
|
272
|
+
end
|