rails-graphql 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (266) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +19 -0
  4. data/Rakefile +31 -0
  5. data/ext/depend +3 -0
  6. data/ext/extconf.rb +57 -0
  7. data/ext/graphqlparser/Ast.cpp +346 -0
  8. data/ext/graphqlparser/Ast.h +1214 -0
  9. data/ext/graphqlparser/AstNode.h +36 -0
  10. data/ext/graphqlparser/AstVisitor.h +137 -0
  11. data/ext/graphqlparser/GraphQLParser.cpp +76 -0
  12. data/ext/graphqlparser/GraphQLParser.h +55 -0
  13. data/ext/graphqlparser/JsonVisitor.cpp +161 -0
  14. data/ext/graphqlparser/JsonVisitor.cpp.inc +456 -0
  15. data/ext/graphqlparser/JsonVisitor.h +121 -0
  16. data/ext/graphqlparser/JsonVisitor.h.inc +110 -0
  17. data/ext/graphqlparser/VERSION +1 -0
  18. data/ext/graphqlparser/c/GraphQLAst.cpp +324 -0
  19. data/ext/graphqlparser/c/GraphQLAst.h +180 -0
  20. data/ext/graphqlparser/c/GraphQLAstForEachConcreteType.h +44 -0
  21. data/ext/graphqlparser/c/GraphQLAstNode.cpp +25 -0
  22. data/ext/graphqlparser/c/GraphQLAstNode.h +33 -0
  23. data/ext/graphqlparser/c/GraphQLAstToJSON.cpp +21 -0
  24. data/ext/graphqlparser/c/GraphQLAstToJSON.h +24 -0
  25. data/ext/graphqlparser/c/GraphQLAstVisitor.cpp +55 -0
  26. data/ext/graphqlparser/c/GraphQLAstVisitor.h +53 -0
  27. data/ext/graphqlparser/c/GraphQLParser.cpp +35 -0
  28. data/ext/graphqlparser/c/GraphQLParser.h +54 -0
  29. data/ext/graphqlparser/dump_json_ast.cpp +48 -0
  30. data/ext/graphqlparser/lexer.lpp +324 -0
  31. data/ext/graphqlparser/parser.ypp +693 -0
  32. data/ext/graphqlparser/parsergen/lexer.cpp +2633 -0
  33. data/ext/graphqlparser/parsergen/lexer.h +528 -0
  34. data/ext/graphqlparser/parsergen/location.hh +189 -0
  35. data/ext/graphqlparser/parsergen/parser.tab.cpp +3300 -0
  36. data/ext/graphqlparser/parsergen/parser.tab.hpp +646 -0
  37. data/ext/graphqlparser/parsergen/position.hh +179 -0
  38. data/ext/graphqlparser/parsergen/stack.hh +156 -0
  39. data/ext/graphqlparser/syntaxdefs.h +19 -0
  40. data/ext/libgraphqlparser/AstNode.h +36 -0
  41. data/ext/libgraphqlparser/CMakeLists.txt +148 -0
  42. data/ext/libgraphqlparser/CONTRIBUTING.md +23 -0
  43. data/ext/libgraphqlparser/GraphQLParser.cpp +76 -0
  44. data/ext/libgraphqlparser/GraphQLParser.h +55 -0
  45. data/ext/libgraphqlparser/JsonVisitor.cpp +161 -0
  46. data/ext/libgraphqlparser/JsonVisitor.h +121 -0
  47. data/ext/libgraphqlparser/LICENSE +22 -0
  48. data/ext/libgraphqlparser/README.clang-tidy +7 -0
  49. data/ext/libgraphqlparser/README.md +84 -0
  50. data/ext/libgraphqlparser/ast/ast.ast +203 -0
  51. data/ext/libgraphqlparser/ast/ast.py +61 -0
  52. data/ext/libgraphqlparser/ast/c.py +100 -0
  53. data/ext/libgraphqlparser/ast/c.pyc +0 -0
  54. data/ext/libgraphqlparser/ast/c_impl.py +61 -0
  55. data/ext/libgraphqlparser/ast/c_impl.pyc +0 -0
  56. data/ext/libgraphqlparser/ast/c_visitor_impl.py +39 -0
  57. data/ext/libgraphqlparser/ast/c_visitor_impl.pyc +0 -0
  58. data/ext/libgraphqlparser/ast/casing.py +26 -0
  59. data/ext/libgraphqlparser/ast/casing.pyc +0 -0
  60. data/ext/libgraphqlparser/ast/cxx.py +197 -0
  61. data/ext/libgraphqlparser/ast/cxx.pyc +0 -0
  62. data/ext/libgraphqlparser/ast/cxx_impl.py +61 -0
  63. data/ext/libgraphqlparser/ast/cxx_impl.pyc +0 -0
  64. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.py +42 -0
  65. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.pyc +0 -0
  66. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.py +80 -0
  67. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.pyc +0 -0
  68. data/ext/libgraphqlparser/ast/cxx_visitor.py +64 -0
  69. data/ext/libgraphqlparser/ast/cxx_visitor.pyc +0 -0
  70. data/ext/libgraphqlparser/ast/js.py +65 -0
  71. data/ext/libgraphqlparser/ast/license.py +10 -0
  72. data/ext/libgraphqlparser/ast/license.pyc +0 -0
  73. data/ext/libgraphqlparser/c/GraphQLAstNode.cpp +25 -0
  74. data/ext/libgraphqlparser/c/GraphQLAstNode.h +33 -0
  75. data/ext/libgraphqlparser/c/GraphQLAstToJSON.cpp +21 -0
  76. data/ext/libgraphqlparser/c/GraphQLAstToJSON.h +24 -0
  77. data/ext/libgraphqlparser/c/GraphQLAstVisitor.cpp +55 -0
  78. data/ext/libgraphqlparser/c/GraphQLAstVisitor.h +53 -0
  79. data/ext/libgraphqlparser/c/GraphQLParser.cpp +35 -0
  80. data/ext/libgraphqlparser/c/GraphQLParser.h +54 -0
  81. data/ext/libgraphqlparser/clang-tidy-all.sh +3 -0
  82. data/ext/libgraphqlparser/cmake/version.cmake +16 -0
  83. data/ext/libgraphqlparser/dump_json_ast.cpp +48 -0
  84. data/ext/libgraphqlparser/go/README.md +20 -0
  85. data/ext/libgraphqlparser/go/callbacks.go +18 -0
  86. data/ext/libgraphqlparser/go/gotest.go +64 -0
  87. data/ext/libgraphqlparser/lexer.lpp +324 -0
  88. data/ext/libgraphqlparser/libgraphqlparser.pc.in +11 -0
  89. data/ext/libgraphqlparser/parser.ypp +693 -0
  90. data/ext/libgraphqlparser/parsergen/lexer.cpp +2633 -0
  91. data/ext/libgraphqlparser/parsergen/lexer.h +528 -0
  92. data/ext/libgraphqlparser/parsergen/location.hh +189 -0
  93. data/ext/libgraphqlparser/parsergen/parser.tab.cpp +3300 -0
  94. data/ext/libgraphqlparser/parsergen/parser.tab.hpp +646 -0
  95. data/ext/libgraphqlparser/parsergen/position.hh +179 -0
  96. data/ext/libgraphqlparser/parsergen/stack.hh +156 -0
  97. data/ext/libgraphqlparser/python/CMakeLists.txt +14 -0
  98. data/ext/libgraphqlparser/python/README.md +5 -0
  99. data/ext/libgraphqlparser/python/example.py +31 -0
  100. data/ext/libgraphqlparser/syntaxdefs.h +19 -0
  101. data/ext/libgraphqlparser/test/BuildCAPI.c +5 -0
  102. data/ext/libgraphqlparser/test/CMakeLists.txt +25 -0
  103. data/ext/libgraphqlparser/test/JsonVisitorTests.cpp +28 -0
  104. data/ext/libgraphqlparser/test/ParserTests.cpp +352 -0
  105. data/ext/libgraphqlparser/test/kitchen-sink.graphql +59 -0
  106. data/ext/libgraphqlparser/test/kitchen-sink.json +1 -0
  107. data/ext/libgraphqlparser/test/schema-kitchen-sink.graphql +78 -0
  108. data/ext/libgraphqlparser/test/schema-kitchen-sink.json +1 -0
  109. data/ext/libgraphqlparser/test/valgrind.supp +33 -0
  110. data/ext/version.cpp +21 -0
  111. data/lib/generators/graphql/controller_generator.rb +22 -0
  112. data/lib/generators/graphql/schema_generator.rb +22 -0
  113. data/lib/generators/graphql/templates/controller.erb +5 -0
  114. data/lib/generators/graphql/templates/schema.erb +6 -0
  115. data/lib/graphqlparser.so +0 -0
  116. data/lib/rails-graphql.rb +2 -0
  117. data/lib/rails/graphql.rake +1 -0
  118. data/lib/rails/graphql.rb +185 -0
  119. data/lib/rails/graphql/adapters/mysql_adapter.rb +0 -0
  120. data/lib/rails/graphql/adapters/pg_adapter.rb +50 -0
  121. data/lib/rails/graphql/adapters/sqlite_adapter.rb +39 -0
  122. data/lib/rails/graphql/argument.rb +220 -0
  123. data/lib/rails/graphql/callback.rb +124 -0
  124. data/lib/rails/graphql/collectors.rb +14 -0
  125. data/lib/rails/graphql/collectors/hash_collector.rb +83 -0
  126. data/lib/rails/graphql/collectors/idented_collector.rb +73 -0
  127. data/lib/rails/graphql/collectors/json_collector.rb +114 -0
  128. data/lib/rails/graphql/config.rb +61 -0
  129. data/lib/rails/graphql/directive.rb +203 -0
  130. data/lib/rails/graphql/directive/deprecated_directive.rb +59 -0
  131. data/lib/rails/graphql/directive/include_directive.rb +24 -0
  132. data/lib/rails/graphql/directive/skip_directive.rb +24 -0
  133. data/lib/rails/graphql/errors.rb +42 -0
  134. data/lib/rails/graphql/event.rb +141 -0
  135. data/lib/rails/graphql/field.rb +318 -0
  136. data/lib/rails/graphql/field/input_field.rb +92 -0
  137. data/lib/rails/graphql/field/mutation_field.rb +52 -0
  138. data/lib/rails/graphql/field/output_field.rb +96 -0
  139. data/lib/rails/graphql/field/proxied_field.rb +131 -0
  140. data/lib/rails/graphql/field/resolved_field.rb +96 -0
  141. data/lib/rails/graphql/field/scoped_config.rb +22 -0
  142. data/lib/rails/graphql/field/typed_field.rb +104 -0
  143. data/lib/rails/graphql/helpers.rb +40 -0
  144. data/lib/rails/graphql/helpers/attribute_delegator.rb +39 -0
  145. data/lib/rails/graphql/helpers/inherited_collection.rb +152 -0
  146. data/lib/rails/graphql/helpers/leaf_from_ar.rb +141 -0
  147. data/lib/rails/graphql/helpers/registerable.rb +103 -0
  148. data/lib/rails/graphql/helpers/with_arguments.rb +125 -0
  149. data/lib/rails/graphql/helpers/with_assignment.rb +113 -0
  150. data/lib/rails/graphql/helpers/with_callbacks.rb +55 -0
  151. data/lib/rails/graphql/helpers/with_directives.rb +126 -0
  152. data/lib/rails/graphql/helpers/with_events.rb +81 -0
  153. data/lib/rails/graphql/helpers/with_fields.rb +141 -0
  154. data/lib/rails/graphql/helpers/with_namespace.rb +40 -0
  155. data/lib/rails/graphql/helpers/with_owner.rb +35 -0
  156. data/lib/rails/graphql/helpers/with_schema_fields.rb +230 -0
  157. data/lib/rails/graphql/helpers/with_validator.rb +52 -0
  158. data/lib/rails/graphql/introspection.rb +53 -0
  159. data/lib/rails/graphql/native.rb +56 -0
  160. data/lib/rails/graphql/native/functions.rb +38 -0
  161. data/lib/rails/graphql/native/location.rb +41 -0
  162. data/lib/rails/graphql/native/pointers.rb +23 -0
  163. data/lib/rails/graphql/native/visitor.rb +349 -0
  164. data/lib/rails/graphql/railtie.rb +85 -0
  165. data/lib/rails/graphql/railties/base_generator.rb +35 -0
  166. data/lib/rails/graphql/railties/controller.rb +101 -0
  167. data/lib/rails/graphql/railties/controller_runtime.rb +40 -0
  168. data/lib/rails/graphql/railties/log_subscriber.rb +62 -0
  169. data/lib/rails/graphql/request.rb +343 -0
  170. data/lib/rails/graphql/request/arguments.rb +93 -0
  171. data/lib/rails/graphql/request/component.rb +100 -0
  172. data/lib/rails/graphql/request/component/field.rb +225 -0
  173. data/lib/rails/graphql/request/component/fragment.rb +118 -0
  174. data/lib/rails/graphql/request/component/operation.rb +178 -0
  175. data/lib/rails/graphql/request/component/operation/subscription.rb +16 -0
  176. data/lib/rails/graphql/request/component/spread.rb +119 -0
  177. data/lib/rails/graphql/request/component/typename.rb +82 -0
  178. data/lib/rails/graphql/request/context.rb +51 -0
  179. data/lib/rails/graphql/request/errors.rb +54 -0
  180. data/lib/rails/graphql/request/event.rb +112 -0
  181. data/lib/rails/graphql/request/helpers/directives.rb +64 -0
  182. data/lib/rails/graphql/request/helpers/selection_set.rb +87 -0
  183. data/lib/rails/graphql/request/helpers/value_writers.rb +115 -0
  184. data/lib/rails/graphql/request/steps/organizable.rb +146 -0
  185. data/lib/rails/graphql/request/steps/prepareable.rb +33 -0
  186. data/lib/rails/graphql/request/steps/resolveable.rb +32 -0
  187. data/lib/rails/graphql/request/strategy.rb +249 -0
  188. data/lib/rails/graphql/request/strategy/dynamic_instance.rb +41 -0
  189. data/lib/rails/graphql/request/strategy/multi_query_strategy.rb +36 -0
  190. data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +28 -0
  191. data/lib/rails/graphql/schema.rb +272 -0
  192. data/lib/rails/graphql/shortcuts.rb +77 -0
  193. data/lib/rails/graphql/source.rb +371 -0
  194. data/lib/rails/graphql/source/active_record/builders.rb +154 -0
  195. data/lib/rails/graphql/source/active_record_source.rb +231 -0
  196. data/lib/rails/graphql/source/scoped_arguments.rb +87 -0
  197. data/lib/rails/graphql/to_gql.rb +368 -0
  198. data/lib/rails/graphql/type.rb +138 -0
  199. data/lib/rails/graphql/type/enum.rb +206 -0
  200. data/lib/rails/graphql/type/enum/directive_location_enum.rb +30 -0
  201. data/lib/rails/graphql/type/enum/type_kind_enum.rb +57 -0
  202. data/lib/rails/graphql/type/input.rb +134 -0
  203. data/lib/rails/graphql/type/interface.rb +82 -0
  204. data/lib/rails/graphql/type/object.rb +111 -0
  205. data/lib/rails/graphql/type/object/directive_object.rb +34 -0
  206. data/lib/rails/graphql/type/object/enum_value_object.rb +25 -0
  207. data/lib/rails/graphql/type/object/field_object.rb +54 -0
  208. data/lib/rails/graphql/type/object/input_value_object.rb +49 -0
  209. data/lib/rails/graphql/type/object/schema_object.rb +40 -0
  210. data/lib/rails/graphql/type/object/type_object.rb +136 -0
  211. data/lib/rails/graphql/type/scalar.rb +71 -0
  212. data/lib/rails/graphql/type/scalar/bigint_scalar.rb +34 -0
  213. data/lib/rails/graphql/type/scalar/binary_scalar.rb +30 -0
  214. data/lib/rails/graphql/type/scalar/boolean_scalar.rb +37 -0
  215. data/lib/rails/graphql/type/scalar/date_scalar.rb +34 -0
  216. data/lib/rails/graphql/type/scalar/date_time_scalar.rb +32 -0
  217. data/lib/rails/graphql/type/scalar/decimal_scalar.rb +35 -0
  218. data/lib/rails/graphql/type/scalar/float_scalar.rb +32 -0
  219. data/lib/rails/graphql/type/scalar/id_scalar.rb +39 -0
  220. data/lib/rails/graphql/type/scalar/int_scalar.rb +36 -0
  221. data/lib/rails/graphql/type/scalar/string_scalar.rb +28 -0
  222. data/lib/rails/graphql/type/scalar/time_scalar.rb +40 -0
  223. data/lib/rails/graphql/type/union.rb +87 -0
  224. data/lib/rails/graphql/type_map.rb +347 -0
  225. data/lib/rails/graphql/version.rb +7 -0
  226. data/test/assets/introspection-db.json +0 -0
  227. data/test/assets/introspection-mem.txt +1 -0
  228. data/test/assets/introspection.gql +91 -0
  229. data/test/assets/luke.jpg +0 -0
  230. data/test/assets/mem.gql +428 -0
  231. data/test/assets/sqlite.gql +423 -0
  232. data/test/config.rb +80 -0
  233. data/test/graphql/request/context_test.rb +70 -0
  234. data/test/graphql/schema_test.rb +190 -0
  235. data/test/graphql/source_test.rb +237 -0
  236. data/test/graphql/type/enum_test.rb +203 -0
  237. data/test/graphql/type/input_test.rb +138 -0
  238. data/test/graphql/type/interface_test.rb +72 -0
  239. data/test/graphql/type/object_test.rb +104 -0
  240. data/test/graphql/type/scalar/bigint_scalar_test.rb +42 -0
  241. data/test/graphql/type/scalar/binary_scalar_test.rb +17 -0
  242. data/test/graphql/type/scalar/boolean_scalar_test.rb +40 -0
  243. data/test/graphql/type/scalar/date_scalar_test.rb +29 -0
  244. data/test/graphql/type/scalar/date_time_scalar_test.rb +29 -0
  245. data/test/graphql/type/scalar/decimal_scalar_test.rb +28 -0
  246. data/test/graphql/type/scalar/float_scalar_test.rb +22 -0
  247. data/test/graphql/type/scalar/id_scalar_test.rb +26 -0
  248. data/test/graphql/type/scalar/int_scalar_test.rb +26 -0
  249. data/test/graphql/type/scalar/string_scalar_test.rb +17 -0
  250. data/test/graphql/type/scalar/time_scalar_test.rb +36 -0
  251. data/test/graphql/type/scalar_test.rb +45 -0
  252. data/test/graphql/type/union_test.rb +82 -0
  253. data/test/graphql/type_map_test.rb +362 -0
  254. data/test/graphql/type_test.rb +68 -0
  255. data/test/graphql_test.rb +55 -0
  256. data/test/integration/config.rb +56 -0
  257. data/test/integration/memory/star_wars_introspection_test.rb +144 -0
  258. data/test/integration/memory/star_wars_query_test.rb +184 -0
  259. data/test/integration/memory/star_wars_validation_test.rb +99 -0
  260. data/test/integration/schemas/memory.rb +232 -0
  261. data/test/integration/schemas/sqlite.rb +82 -0
  262. data/test/integration/sqlite/star_wars_introspection_test.rb +15 -0
  263. data/test/integration/sqlite/star_wars_mutation_test.rb +82 -0
  264. data/test/integration/sqlite/star_wars_query_test.rb +71 -0
  265. data/test/test_ext.rb +48 -0
  266. 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