rails-graphql 0.2.1 → 1.0.0.beta

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.
Files changed (297) hide show
  1. checksums.yaml +4 -4
  2. data/ext/console.rb +18 -0
  3. data/ext/extconf.h +3 -0
  4. data/ext/extconf.rb +1 -54
  5. data/ext/gql_parser.c +646 -0
  6. data/ext/shared.c +482 -0
  7. data/ext/shared.h +177 -0
  8. data/lib/gql_parser.so +0 -0
  9. data/lib/rails/graphql/adapters/mysql_adapter.rb +59 -0
  10. data/lib/rails/graphql/adapters/pg_adapter.rb +25 -22
  11. data/lib/rails/graphql/adapters/sqlite_adapter.rb +17 -14
  12. data/lib/rails/graphql/alternative/field_set.rb +36 -0
  13. data/lib/rails/graphql/alternative/mutation.rb +17 -0
  14. data/lib/rails/graphql/alternative/query.rb +93 -0
  15. data/lib/rails/graphql/alternative/subscription.rb +17 -0
  16. data/lib/rails/graphql/alternative.rb +20 -0
  17. data/lib/rails/graphql/argument.rb +21 -24
  18. data/lib/rails/graphql/callback.rb +24 -9
  19. data/lib/rails/graphql/collectors/hash_collector.rb +14 -6
  20. data/lib/rails/graphql/collectors/idented_collector.rb +10 -7
  21. data/lib/rails/graphql/collectors/json_collector.rb +22 -17
  22. data/lib/rails/graphql/collectors.rb +4 -4
  23. data/lib/rails/graphql/config.rb +130 -15
  24. data/lib/rails/graphql/directive/cached_directive.rb +33 -0
  25. data/lib/rails/graphql/directive/deprecated_directive.rb +10 -10
  26. data/lib/rails/graphql/directive/include_directive.rb +5 -4
  27. data/lib/rails/graphql/directive/skip_directive.rb +5 -4
  28. data/lib/rails/graphql/directive.rb +118 -63
  29. data/lib/rails/graphql/errors.rb +33 -4
  30. data/lib/rails/graphql/event.rb +16 -5
  31. data/lib/rails/graphql/field/authorized_field.rb +19 -3
  32. data/lib/rails/graphql/field/input_field.rb +11 -10
  33. data/lib/rails/graphql/field/mutation_field.rb +42 -7
  34. data/lib/rails/graphql/field/output_field.rb +102 -13
  35. data/lib/rails/graphql/field/proxied_field.rb +31 -22
  36. data/lib/rails/graphql/field/resolved_field.rb +26 -24
  37. data/lib/rails/graphql/field/scoped_config.rb +10 -4
  38. data/lib/rails/graphql/field/subscription_field.rb +140 -0
  39. data/lib/rails/graphql/field/typed_field.rb +43 -22
  40. data/lib/rails/graphql/field.rb +70 -56
  41. data/lib/rails/graphql/global_id.rb +85 -0
  42. data/lib/rails/graphql/helpers/attribute_delegator.rb +5 -5
  43. data/lib/rails/graphql/helpers/inherited_collection/array.rb +50 -0
  44. data/lib/rails/graphql/helpers/inherited_collection/base.rb +43 -0
  45. data/lib/rails/graphql/helpers/inherited_collection/hash.rb +87 -0
  46. data/lib/rails/graphql/helpers/inherited_collection.rb +25 -76
  47. data/lib/rails/graphql/helpers/instantiable.rb +15 -0
  48. data/lib/rails/graphql/helpers/leaf_from_ar.rb +7 -7
  49. data/lib/rails/graphql/helpers/registerable.rb +44 -62
  50. data/lib/rails/graphql/helpers/unregisterable.rb +16 -0
  51. data/lib/rails/graphql/helpers/with_arguments.rb +31 -27
  52. data/lib/rails/graphql/helpers/with_assignment.rb +6 -6
  53. data/lib/rails/graphql/helpers/with_callbacks.rb +25 -8
  54. data/lib/rails/graphql/helpers/with_description.rb +71 -0
  55. data/lib/rails/graphql/helpers/with_directives.rb +54 -30
  56. data/lib/rails/graphql/helpers/with_events.rb +21 -23
  57. data/lib/rails/graphql/helpers/with_fields.rb +76 -22
  58. data/lib/rails/graphql/helpers/with_global_id.rb +22 -0
  59. data/lib/rails/graphql/helpers/with_name.rb +43 -0
  60. data/lib/rails/graphql/helpers/with_namespace.rb +7 -4
  61. data/lib/rails/graphql/helpers/with_owner.rb +8 -7
  62. data/lib/rails/graphql/helpers/with_schema_fields.rb +137 -55
  63. data/lib/rails/graphql/helpers/with_validator.rb +9 -9
  64. data/lib/rails/graphql/helpers.rb +10 -3
  65. data/lib/rails/graphql/introspection.rb +43 -36
  66. data/lib/rails/graphql/railtie.rb +88 -33
  67. data/lib/rails/graphql/railties/base_generator.rb +3 -9
  68. data/lib/rails/graphql/railties/channel.rb +157 -0
  69. data/lib/rails/graphql/railties/controller.rb +62 -17
  70. data/lib/rails/graphql/railties/controller_runtime.rb +5 -5
  71. data/lib/rails/graphql/railties/log_subscriber.rb +81 -14
  72. data/lib/rails/graphql/request/arguments.rb +24 -49
  73. data/lib/rails/graphql/request/backtrace.rb +191 -0
  74. data/lib/rails/graphql/request/component/field.rb +86 -65
  75. data/lib/rails/graphql/request/component/fragment.rb +72 -24
  76. data/lib/rails/graphql/request/component/operation/subscription.rb +164 -4
  77. data/lib/rails/graphql/request/component/operation.rb +63 -31
  78. data/lib/rails/graphql/request/component/spread.rb +68 -25
  79. data/lib/rails/graphql/request/component/typename.rb +27 -12
  80. data/lib/rails/graphql/request/component.rb +75 -36
  81. data/lib/rails/graphql/request/context.rb +18 -8
  82. data/lib/rails/graphql/request/errors.rb +16 -6
  83. data/lib/rails/graphql/request/event.rb +19 -8
  84. data/lib/rails/graphql/request/helpers/directives.rb +68 -27
  85. data/lib/rails/graphql/request/helpers/selection_set.rb +51 -25
  86. data/lib/rails/graphql/request/helpers/value_writers.rb +18 -16
  87. data/lib/rails/graphql/request/prepared_data.rb +98 -0
  88. data/lib/rails/graphql/request/steps/authorizable.rb +24 -14
  89. data/lib/rails/graphql/request/steps/organizable.rb +110 -48
  90. data/lib/rails/graphql/request/steps/{prepareable.rb → preparable.rb} +20 -7
  91. data/lib/rails/graphql/request/steps/{resolveable.rb → resolvable.rb} +15 -6
  92. data/lib/rails/graphql/request/strategy/cached_strategy.rb +64 -0
  93. data/lib/rails/graphql/request/strategy/dynamic_instance.rb +6 -6
  94. data/lib/rails/graphql/request/strategy/multi_query_strategy.rb +6 -13
  95. data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +9 -9
  96. data/lib/rails/graphql/request/strategy.rb +131 -75
  97. data/lib/rails/graphql/request/subscription.rb +80 -0
  98. data/lib/rails/graphql/request.rb +305 -86
  99. data/lib/rails/graphql/schema.rb +240 -48
  100. data/lib/rails/graphql/shortcuts.rb +22 -3
  101. data/lib/rails/graphql/source/active_record/builders.rb +49 -35
  102. data/lib/rails/graphql/source/active_record_source.rb +70 -54
  103. data/lib/rails/graphql/source/base.rb +111 -0
  104. data/lib/rails/graphql/source/builder.rb +128 -0
  105. data/lib/rails/graphql/source/scoped_arguments.rb +31 -19
  106. data/lib/rails/graphql/source.rb +89 -213
  107. data/lib/rails/graphql/subscription/provider/action_cable.rb +112 -0
  108. data/lib/rails/graphql/subscription/provider/base.rb +191 -0
  109. data/lib/rails/graphql/subscription/provider.rb +18 -0
  110. data/lib/rails/graphql/subscription/store/base.rb +145 -0
  111. data/lib/rails/graphql/subscription/store/memory.rb +127 -0
  112. data/lib/rails/graphql/subscription/store.rb +19 -0
  113. data/lib/rails/graphql/subscription.rb +17 -0
  114. data/lib/rails/graphql/to_gql.rb +29 -32
  115. data/lib/rails/graphql/type/enum/directive_location_enum.rb +11 -11
  116. data/lib/rails/graphql/type/enum/type_kind_enum.rb +3 -3
  117. data/lib/rails/graphql/type/enum.rb +34 -48
  118. data/lib/rails/graphql/type/input.rb +74 -23
  119. data/lib/rails/graphql/type/interface.rb +16 -26
  120. data/lib/rails/graphql/type/object/directive_object.rb +4 -4
  121. data/lib/rails/graphql/type/object/enum_value_object.rb +3 -3
  122. data/lib/rails/graphql/type/object/field_object.rb +24 -6
  123. data/lib/rails/graphql/type/object/input_value_object.rb +3 -3
  124. data/lib/rails/graphql/type/object/schema_object.rb +5 -8
  125. data/lib/rails/graphql/type/object/type_object.rb +29 -19
  126. data/lib/rails/graphql/type/object.rb +26 -23
  127. data/lib/rails/graphql/type/scalar/any_scalar.rb +30 -0
  128. data/lib/rails/graphql/type/scalar/bigint_scalar.rb +5 -5
  129. data/lib/rails/graphql/type/scalar/binary_scalar.rb +3 -3
  130. data/lib/rails/graphql/type/scalar/boolean_scalar.rb +8 -8
  131. data/lib/rails/graphql/type/scalar/date_scalar.rb +3 -3
  132. data/lib/rails/graphql/type/scalar/date_time_scalar.rb +3 -3
  133. data/lib/rails/graphql/type/scalar/decimal_scalar.rb +3 -3
  134. data/lib/rails/graphql/type/scalar/float_scalar.rb +5 -5
  135. data/lib/rails/graphql/type/scalar/id_scalar.rb +6 -5
  136. data/lib/rails/graphql/type/scalar/int_scalar.rb +6 -5
  137. data/lib/rails/graphql/type/scalar/json_scalar.rb +39 -0
  138. data/lib/rails/graphql/type/scalar/string_scalar.rb +18 -4
  139. data/lib/rails/graphql/type/scalar/time_scalar.rb +5 -5
  140. data/lib/rails/graphql/type/scalar.rb +25 -22
  141. data/lib/rails/graphql/type/union.rb +14 -16
  142. data/lib/rails/graphql/type.rb +34 -25
  143. data/lib/rails/graphql/type_map.rb +256 -164
  144. data/lib/rails/graphql/uri.rb +166 -0
  145. data/lib/rails/graphql/version.rb +15 -3
  146. data/lib/rails/graphql.rake +3 -0
  147. data/lib/rails/graphql.rb +85 -52
  148. data/lib/rails-graphql.rb +1 -1
  149. data/test/assets/en.yml +29 -0
  150. data/test/assets/introspection-mem.txt +1 -1
  151. data/test/assets/mem.gql +18 -45
  152. data/test/assets/mysql.gql +392 -0
  153. data/test/assets/sqlite.gql +21 -12
  154. data/test/assets/translate.gql +335 -0
  155. data/test/config.rb +18 -8
  156. data/test/graphql/schema_test.rb +12 -19
  157. data/test/graphql/source_test.rb +8 -75
  158. data/test/graphql/type/enum_test.rb +207 -203
  159. data/test/graphql/type/input_test.rb +14 -9
  160. data/test/graphql/type/interface_test.rb +4 -4
  161. data/test/graphql/type/scalar/any_scalar_test.rb +38 -0
  162. data/test/graphql/type/scalar/boolean_scalar_test.rb +6 -3
  163. data/test/graphql/type/scalar/json_scalar_test.rb +23 -0
  164. data/test/graphql/type_map_test.rb +51 -66
  165. data/test/graphql/type_test.rb +0 -19
  166. data/test/graphql_test.rb +1 -1
  167. data/test/integration/{authorization/authorization_test.rb → authorization_test.rb} +40 -14
  168. data/test/integration/config.rb +36 -3
  169. data/test/integration/customization_test.rb +39 -0
  170. data/test/integration/global_id_test.rb +99 -0
  171. data/test/integration/memory/star_wars_introspection_test.rb +24 -16
  172. data/test/integration/memory/star_wars_query_test.rb +54 -3
  173. data/test/integration/memory/star_wars_validation_test.rb +1 -1
  174. data/test/integration/mysql/star_wars_introspection_test.rb +25 -0
  175. data/test/integration/persisted_query_test.rb +87 -0
  176. data/test/integration/resolver_precedence_test.rb +154 -0
  177. data/test/integration/schemas/memory.rb +22 -7
  178. data/test/integration/schemas/mysql.rb +62 -0
  179. data/test/integration/schemas/sqlite.rb +21 -12
  180. data/test/integration/sqlite/star_wars_global_id_test.rb +83 -0
  181. data/test/integration/sqlite/star_wars_introspection_test.rb +10 -0
  182. data/test/integration/sqlite/star_wars_query_test.rb +14 -1
  183. data/test/integration/translate_test.rb +61 -0
  184. data/test/test_ext.rb +16 -13
  185. metadata +108 -157
  186. data/ext/depend +0 -3
  187. data/ext/graphqlparser/Ast.cpp +0 -346
  188. data/ext/graphqlparser/Ast.h +0 -1214
  189. data/ext/graphqlparser/AstNode.h +0 -36
  190. data/ext/graphqlparser/AstVisitor.h +0 -137
  191. data/ext/graphqlparser/GraphQLParser.cpp +0 -76
  192. data/ext/graphqlparser/GraphQLParser.h +0 -55
  193. data/ext/graphqlparser/JsonVisitor.cpp +0 -161
  194. data/ext/graphqlparser/JsonVisitor.cpp.inc +0 -456
  195. data/ext/graphqlparser/JsonVisitor.h +0 -121
  196. data/ext/graphqlparser/JsonVisitor.h.inc +0 -110
  197. data/ext/graphqlparser/VERSION +0 -1
  198. data/ext/graphqlparser/c/GraphQLAst.cpp +0 -324
  199. data/ext/graphqlparser/c/GraphQLAst.h +0 -180
  200. data/ext/graphqlparser/c/GraphQLAstForEachConcreteType.h +0 -44
  201. data/ext/graphqlparser/c/GraphQLAstNode.cpp +0 -25
  202. data/ext/graphqlparser/c/GraphQLAstNode.h +0 -33
  203. data/ext/graphqlparser/c/GraphQLAstToJSON.cpp +0 -21
  204. data/ext/graphqlparser/c/GraphQLAstToJSON.h +0 -24
  205. data/ext/graphqlparser/c/GraphQLAstVisitor.cpp +0 -55
  206. data/ext/graphqlparser/c/GraphQLAstVisitor.h +0 -53
  207. data/ext/graphqlparser/c/GraphQLParser.cpp +0 -35
  208. data/ext/graphqlparser/c/GraphQLParser.h +0 -54
  209. data/ext/graphqlparser/dump_json_ast.cpp +0 -48
  210. data/ext/graphqlparser/lexer.lpp +0 -324
  211. data/ext/graphqlparser/parser.ypp +0 -693
  212. data/ext/graphqlparser/parsergen/lexer.cpp +0 -2633
  213. data/ext/graphqlparser/parsergen/lexer.h +0 -528
  214. data/ext/graphqlparser/parsergen/location.hh +0 -189
  215. data/ext/graphqlparser/parsergen/parser.tab.cpp +0 -3300
  216. data/ext/graphqlparser/parsergen/parser.tab.hpp +0 -646
  217. data/ext/graphqlparser/parsergen/position.hh +0 -179
  218. data/ext/graphqlparser/parsergen/stack.hh +0 -156
  219. data/ext/graphqlparser/syntaxdefs.h +0 -19
  220. data/ext/libgraphqlparser/AstNode.h +0 -36
  221. data/ext/libgraphqlparser/CMakeLists.txt +0 -148
  222. data/ext/libgraphqlparser/CONTRIBUTING.md +0 -23
  223. data/ext/libgraphqlparser/GraphQLParser.cpp +0 -76
  224. data/ext/libgraphqlparser/GraphQLParser.h +0 -55
  225. data/ext/libgraphqlparser/JsonVisitor.cpp +0 -161
  226. data/ext/libgraphqlparser/JsonVisitor.h +0 -121
  227. data/ext/libgraphqlparser/LICENSE +0 -22
  228. data/ext/libgraphqlparser/README.clang-tidy +0 -7
  229. data/ext/libgraphqlparser/README.md +0 -84
  230. data/ext/libgraphqlparser/ast/ast.ast +0 -203
  231. data/ext/libgraphqlparser/ast/ast.py +0 -61
  232. data/ext/libgraphqlparser/ast/c.py +0 -100
  233. data/ext/libgraphqlparser/ast/c.pyc +0 -0
  234. data/ext/libgraphqlparser/ast/c_impl.py +0 -61
  235. data/ext/libgraphqlparser/ast/c_impl.pyc +0 -0
  236. data/ext/libgraphqlparser/ast/c_visitor_impl.py +0 -39
  237. data/ext/libgraphqlparser/ast/c_visitor_impl.pyc +0 -0
  238. data/ext/libgraphqlparser/ast/casing.py +0 -26
  239. data/ext/libgraphqlparser/ast/casing.pyc +0 -0
  240. data/ext/libgraphqlparser/ast/cxx.py +0 -197
  241. data/ext/libgraphqlparser/ast/cxx.pyc +0 -0
  242. data/ext/libgraphqlparser/ast/cxx_impl.py +0 -61
  243. data/ext/libgraphqlparser/ast/cxx_impl.pyc +0 -0
  244. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.py +0 -42
  245. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.pyc +0 -0
  246. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.py +0 -80
  247. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.pyc +0 -0
  248. data/ext/libgraphqlparser/ast/cxx_visitor.py +0 -64
  249. data/ext/libgraphqlparser/ast/cxx_visitor.pyc +0 -0
  250. data/ext/libgraphqlparser/ast/js.py +0 -65
  251. data/ext/libgraphqlparser/ast/license.py +0 -10
  252. data/ext/libgraphqlparser/ast/license.pyc +0 -0
  253. data/ext/libgraphqlparser/c/GraphQLAstNode.cpp +0 -25
  254. data/ext/libgraphqlparser/c/GraphQLAstNode.h +0 -33
  255. data/ext/libgraphqlparser/c/GraphQLAstToJSON.cpp +0 -21
  256. data/ext/libgraphqlparser/c/GraphQLAstToJSON.h +0 -24
  257. data/ext/libgraphqlparser/c/GraphQLAstVisitor.cpp +0 -55
  258. data/ext/libgraphqlparser/c/GraphQLAstVisitor.h +0 -53
  259. data/ext/libgraphqlparser/c/GraphQLParser.cpp +0 -35
  260. data/ext/libgraphqlparser/c/GraphQLParser.h +0 -54
  261. data/ext/libgraphqlparser/clang-tidy-all.sh +0 -3
  262. data/ext/libgraphqlparser/cmake/version.cmake +0 -16
  263. data/ext/libgraphqlparser/dump_json_ast.cpp +0 -48
  264. data/ext/libgraphqlparser/go/README.md +0 -20
  265. data/ext/libgraphqlparser/go/callbacks.go +0 -18
  266. data/ext/libgraphqlparser/go/gotest.go +0 -64
  267. data/ext/libgraphqlparser/lexer.lpp +0 -324
  268. data/ext/libgraphqlparser/libgraphqlparser.pc.in +0 -11
  269. data/ext/libgraphqlparser/parser.ypp +0 -693
  270. data/ext/libgraphqlparser/parsergen/lexer.cpp +0 -2633
  271. data/ext/libgraphqlparser/parsergen/lexer.h +0 -528
  272. data/ext/libgraphqlparser/parsergen/location.hh +0 -189
  273. data/ext/libgraphqlparser/parsergen/parser.tab.cpp +0 -3300
  274. data/ext/libgraphqlparser/parsergen/parser.tab.hpp +0 -646
  275. data/ext/libgraphqlparser/parsergen/position.hh +0 -179
  276. data/ext/libgraphqlparser/parsergen/stack.hh +0 -156
  277. data/ext/libgraphqlparser/python/CMakeLists.txt +0 -14
  278. data/ext/libgraphqlparser/python/README.md +0 -5
  279. data/ext/libgraphqlparser/python/example.py +0 -31
  280. data/ext/libgraphqlparser/syntaxdefs.h +0 -19
  281. data/ext/libgraphqlparser/test/BuildCAPI.c +0 -5
  282. data/ext/libgraphqlparser/test/CMakeLists.txt +0 -25
  283. data/ext/libgraphqlparser/test/JsonVisitorTests.cpp +0 -28
  284. data/ext/libgraphqlparser/test/ParserTests.cpp +0 -352
  285. data/ext/libgraphqlparser/test/kitchen-sink.graphql +0 -59
  286. data/ext/libgraphqlparser/test/kitchen-sink.json +0 -1
  287. data/ext/libgraphqlparser/test/schema-kitchen-sink.graphql +0 -78
  288. data/ext/libgraphqlparser/test/schema-kitchen-sink.json +0 -1
  289. data/ext/libgraphqlparser/test/valgrind.supp +0 -33
  290. data/ext/version.cpp +0 -21
  291. data/lib/graphqlparser.so +0 -0
  292. data/lib/rails/graphql/native/functions.rb +0 -38
  293. data/lib/rails/graphql/native/location.rb +0 -41
  294. data/lib/rails/graphql/native/pointers.rb +0 -23
  295. data/lib/rails/graphql/native/visitor.rb +0 -349
  296. data/lib/rails/graphql/native.rb +0 -56
  297. data/test/integration/schemas/authorization.rb +0 -12
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module GraphQL
5
+ class Request
6
+ # = GraphQl Cached Strategy
7
+ #
8
+ # This strategy will process hard cached operations. Soft cached
9
+ # operations are those that only the document is cached in the server and
10
+ # processed via its unique identifier (UUID). Whereas, hard cached
11
+ # operations pretty muchy skips the organize step since that is what is
12
+ # cached.
13
+ #
14
+ # Beware, if the version in the cache is different from the version in the
15
+ # type map, it won't be able to process it.
16
+ class Strategy::CachedStrategy < Strategy
17
+ self.priority = 100
18
+
19
+ class << self
20
+
21
+ # Resolve whenever it has a cache directive on any of the operations
22
+ def can_resolve?(request)
23
+ false
24
+ # request.operations.each_value.any? do |op|
25
+ # op.data&.directives&.any? { |dir| directive_name(dir) == 'cached' }
26
+ # end
27
+ end
28
+
29
+ private
30
+
31
+ def directive_name(obj)
32
+ Native.node_name(Native.directive_name(obj))
33
+ end
34
+ end
35
+
36
+ # Executes the strategy in the normal mode
37
+ def resolve!
38
+ response.with_stack('data') do
39
+ for_each_operation { |op| collect_listeners { op.organize! } }
40
+ for_each_operation { |op| collect_data { op.prepare! } }
41
+ for_each_operation { |op| collect_response { op.resolve! } }
42
+
43
+ # collect_data(true) { op.prepare! }
44
+ # collect_response { op.resolve! }
45
+
46
+ # operations.each_value do |op|
47
+ # collect_listeners { op.organize! }
48
+ # collect_data(true) { op.prepare! }
49
+ # collect_response { op.resolve! }
50
+ # end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ # Execute a given block for each defined operation
57
+ def for_each_operation(&block)
58
+ operations.each_value(&block)
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Rails # :nodoc:
4
- module GraphQL # :nodoc:
5
- class Request # :nodoc:
3
+ module Rails
4
+ module GraphQL
5
+ class Request
6
6
  # = GraphQl Strategy Dynamic Instance
7
7
  #
8
8
  # When an event is call on non-object types, this class allows both
@@ -16,18 +16,18 @@ module Rails # :nodoc:
16
16
 
17
17
  private
18
18
 
19
- def respond_to_missing?(method_name, include_private = false) # :nodoc:
19
+ def respond_to_missing?(method_name, include_private = false)
20
20
  __current_object__&.respond_to?(method_name, include_private) || super
21
21
  end
22
22
 
23
- def method_missing(method_name, *args, **xargs, &block) # :nodoc:
23
+ def method_missing(method_name, *args, **xargs, &block)
24
24
  object = __current_object__
25
25
 
26
26
  return super unless object&.respond_to?(method_name)
27
27
  object.public_send(method_name, *args, **xargs, &block)
28
28
  end
29
29
 
30
- def __current_object__ # :nodoc:
30
+ def __current_object__
31
31
  return unless __getobj__.instance_variable_defined?(:@event)
32
32
 
33
33
  event = __getobj__.instance_variable_get(:@event)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Rails # :nodoc:
4
- module GraphQL # :nodoc:
5
- class Request # :nodoc:
3
+ module Rails
4
+ module GraphQL
5
+ class Request
6
6
  # = GraphQl Multi Query Strategy
7
7
  #
8
8
  # This is a resolution strategy to solve requests that only contain
@@ -11,25 +11,18 @@ module Rails # :nodoc:
11
11
  class Strategy::MultiQueryStrategy < Strategy
12
12
  self.priority = 10
13
13
 
14
- def self.can_resolve?(request) # :nodoc:
15
- request.operations.values.all?(&:query?)
14
+ def self.can_resolve?(request)
15
+ request.operations.each_value.all? { |op| op.of_type?(:query) }
16
16
  end
17
17
 
18
18
  # Executes the strategy in the normal mode
19
19
  def resolve!
20
- response.with_stack(:data) do
20
+ response.with_stack('data') do
21
21
  for_each_operation { |op| collect_listeners { op.organize! } }
22
22
  for_each_operation { |op| collect_data { op.prepare! } }
23
23
  for_each_operation { |op| collect_response { op.resolve! } }
24
24
  end
25
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
26
  end
34
27
  end
35
28
  end
@@ -1,24 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Rails # :nodoc:
4
- module GraphQL # :nodoc:
5
- class Request # :nodoc:
3
+ module Rails
4
+ module GraphQL
5
+ class Request
6
6
  # = GraphQl Sequenced Strategy
7
7
  #
8
8
  # This is the default resolution strategy, where each operation is
9
9
  # performed in sequece, and they don't relate to each other in any way.
10
10
  class Strategy::SequencedStrategy < Strategy
11
- def self.can_resolve?(_) # :nodoc:
11
+ def self.can_resolve?(_)
12
12
  true
13
13
  end
14
14
 
15
15
  # Executes the strategy in the normal mode
16
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! }
17
+ response.with_stack('data') do
18
+ for_each_operation do |op|
19
+ collect_listeners { op.organize! }
20
+ collect_data(true) { op.prepare! }
21
+ collect_response { op.resolve! }
22
22
  end
23
23
  end
24
24
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Rails # :nodoc:
4
- module GraphQL # :nodoc:
5
- class Request # :nodoc:
3
+ module Rails
4
+ module GraphQL
5
+ class Request
6
6
  # = GraphQL Request Strategy
7
7
  #
8
8
  # This is the base class for the strategies of resolving a request.
@@ -11,10 +11,9 @@ module Rails # :nodoc:
11
11
 
12
12
  autoload :DynamicInstance
13
13
 
14
- eager_autoload do
15
- autoload :SequencedStrategy
16
- autoload :MultiQueryStrategy
17
- end
14
+ autoload :SequencedStrategy
15
+ autoload :MultiQueryStrategy
16
+ autoload :CachedStrategy
18
17
 
19
18
  # Configurations for the prepare step
20
19
  PREPARE_XARGS = { object?: true, reverse?: true }.freeze
@@ -24,7 +23,7 @@ module Rails # :nodoc:
24
23
 
25
24
  delegate :operations, :errors, :response, :schema, to: :request
26
25
 
27
- attr_reader :listeners, :request, :context
26
+ attr_reader :listeners, :request, :context, :stage
28
27
 
29
28
  class << self
30
29
  # Check if the strategy can resolve the given +request+. By default,
@@ -38,7 +37,15 @@ module Rails # :nodoc:
38
37
  def initialize(request)
39
38
  @request = request
40
39
  @objects_pool = {}
41
- collect_request_listeners
40
+ @listeners = Hash.new { |h, k| h[k] = Set.new }
41
+ add_listeners_from(request)
42
+ end
43
+
44
+ # Clear all strategy information
45
+ def clear
46
+ @listeners.clear
47
+ @objects_pool.clear
48
+ @stage = @context = @objects_pool = @data_pool = @listeners = nil
42
49
  end
43
50
 
44
51
  # Executes the strategy in the normal mode
@@ -48,17 +55,12 @@ module Rails # :nodoc:
48
55
 
49
56
  # Find a given +type+ and store it on request cache
50
57
  def find_type!(type)
51
- request.cache(:types)[type] ||= schema.find_type!(type)
58
+ request.nested_cache(:types, type) { schema.find_type!(type) }
52
59
  end
53
60
 
54
61
  # Find a given +directive+ and store it on request cache
55
62
  def find_directive!(directive)
56
- request.cache(:directives)[directive] ||= schema.find_directive!(directive)
57
- end
58
-
59
- # Check if it's enabled to collect listeners
60
- def add_listeners?
61
- !listeners.frozen?
63
+ request.nested_cache(:directives, directive) { schema.find_directive!(directive) }
62
64
  end
63
65
 
64
66
  # Check if any listener were actually added
@@ -79,8 +81,8 @@ module Rails # :nodoc:
79
81
 
80
82
  # When a +field+ has a perform step, run it under the context of the
81
83
  # prepared value from the data pool
82
- def perform(field)
83
- context.stacked(@data_pool[field]) do
84
+ def perform(field, data = nil)
85
+ context.stacked(data || @data_pool[field]) do
84
86
  safe_store_data(field) do
85
87
  Event.trigger(:perform, field, self, &field.performer)
86
88
  end
@@ -91,27 +93,26 @@ module Rails # :nodoc:
91
93
  # block using context stack
92
94
  def prepare(field, &block)
93
95
  value = safe_store_data(field) do
94
- Event.trigger(:prepare, field, self, **PREPARE_XARGS)
96
+ prepared = request.prepared_data_for(field)
97
+ if prepared.is_a?(PreparedData)
98
+ field.prepared_data!
99
+ prepared.all
100
+ else
101
+ Event.trigger(:prepare, field, self, **PREPARE_XARGS)
102
+ end
95
103
  end
96
104
 
97
- return context.stacked(value, &block) unless value.nil?
105
+ perform(field, value) if field.mutation?
98
106
 
99
- stack = field.all_events[:prepare].map { |cb| cb.source_location.join(':') }
100
- request.report_node_error(<<~MSG.squish, field, stack: stack.reverse)
101
- It is expected to get a result from prepare events
102
- MSG
107
+ value = @data_pool[field]
108
+ context.stacked(value, &block) unless value.nil?
103
109
  end
104
110
 
105
111
  # Resolve a value for a given object, It uses the +args+ to prevent
106
112
  # problems with nil values.
107
113
  def resolve(field, *args, array: false, decorate: false, &block)
108
- rescue_with_handler(field: field) do
109
- prepared = data_for(args, field)&.last
110
- args << Event.trigger(:resolve, field, self, prepared: prepared,
111
- &field.resolver) if field.try(:dynamic_resolver?)
112
- end if args.size.zero?
114
+ resolve_data_for(field, args)
113
115
 
114
- # Now we have a value to set on the context
115
116
  value = args.last
116
117
  value = field.decorate(value) if decorate
117
118
  context.stacked(value) do |current|
@@ -124,12 +125,18 @@ module Rails # :nodoc:
124
125
  end
125
126
  end
126
127
 
127
- # Safe trigger an event and ensure to send any exception to the request
128
- # handler
129
- def rescue_with_handler(**extra)
130
- yield
131
- rescue => error
132
- request.rescue_with_handler(error, **extra)
128
+ # Get the resolved data for a given field
129
+ def resolve_data_for(field, args)
130
+ return unless args.size.zero?
131
+
132
+ if field.try(:dynamic_resolver?)
133
+ prepared = prepared_data_for(field)
134
+ args << Event.trigger(:resolve, field, self, prepared: prepared, &field.resolver)
135
+ elsif field.prepared_data?
136
+ args << prepared_data_for(field)
137
+ else
138
+ data_for(args, field)
139
+ end
133
140
  end
134
141
 
135
142
  # Check if the given class is in the pool, or add a new instance to the
@@ -154,20 +161,35 @@ module Rails # :nodoc:
154
161
  def trigger_event(event_name, **xargs)
155
162
  return unless listening_to?(event_name)
156
163
 
157
- objects = listeners[event_name.to_sym] & request.stack
158
- return if objects.empty?
164
+ # A simpler attempt to remove select less objects (or even none) by
165
+ # assuming that the first item will work as exclusive and
166
+ # non-exclusive, and the others, only non-exclusive or anything
167
+ # different than a +Callback+
168
+ event_name = event_name.to_sym
169
+ list = listeners[event_name]
170
+ objects = request.stack.select.with_index do |obj, idx|
171
+ next unless list.include?(obj)
172
+ next true if idx == 0
173
+
174
+ obj.all_events.try(:[], event_name)&.any? do |ev|
175
+ !(ev.is_a?(Callback) && ev.exclusive?)
176
+ end
177
+ end
159
178
 
160
- Event.trigger(event_name, objects, self, **xargs)
179
+ # Now trigger with more for all the selected objects
180
+ Event.trigger(event_name, objects, self, **xargs) if objects.present?
161
181
  end
162
182
 
163
183
  # Check what kind of event listeners the object have, in order to speed
164
- # up processing by avoiding unnecesary event instances
165
- def add_listener(object)
166
- return unless add_listeners?
167
-
168
- object.all_listeners.each do |event_name|
184
+ # up processing by avoiding unnecessary event instances
185
+ def add_listeners_from(object)
186
+ object.all_listeners&.each do |event_name|
169
187
  listeners[event_name] << object
170
188
  end
189
+
190
+ if request.prepared_data_for?(object)
191
+ listeners[:prepare] << object
192
+ end
171
193
  end
172
194
 
173
195
  # Store a given resolve +value+ for a given +field+
@@ -177,48 +199,93 @@ module Rails # :nodoc:
177
199
 
178
200
  # Only store a given +value+ for a given +field+ if it is not set yet
179
201
  def safe_store_data(field, value = nil)
180
- rescue_with_handler(field: field) do
181
- value ||= yield if block_given?
182
- @data_pool[field] ||= value unless value.nil?
202
+ value ||= yield if block_given?
203
+ @data_pool[field] ||= value unless value.nil?
204
+ end
205
+
206
+ # Get the prepared data for the given +field+, getting ready for
207
+ # resolve, while ensuring to check prepared data on request
208
+ def prepared_data_for(field)
209
+ return @data_pool[field] unless field.prepared_data?
210
+
211
+ prepared = request.prepared_data_for(field).next
212
+ prepared unless prepared === PreparedData::NULL
213
+ end
214
+
215
+ # Simply run the organize step for compilation
216
+ def compile
217
+ for_each_operation { |op| collect_listeners { op.organize! } }
218
+ end
219
+
220
+ # Build the cache object
221
+ def cache_dump
222
+ { class: self.class }
223
+ end
224
+
225
+ # Organize from cache data
226
+ def cache_load(data)
227
+ data, operations, fragments = data.values_at(:strategy, :operations, :fragments)
228
+
229
+ collect_listeners do
230
+ # Load all operations
231
+ operations = operations.transform_values do |operation|
232
+ request.build_from_cache(operation.delete(:type)).tap do |instance|
233
+ instance.instance_variable_set(:@request, request)
234
+ instance.cache_load(operation)
235
+ end
236
+ end
237
+
238
+ # Load all fragments
239
+ fragments = fragments&.transform_values do |fragment|
240
+ request.build_from_cache(Component::Fragment).tap do |instance|
241
+ instance.instance_variable_set(:@request, request)
242
+ instance.cache_load(fragment)
243
+ end
244
+ end
183
245
  end
246
+
247
+ # Mark itself as already organized
248
+ @organized = true
249
+
250
+ # Save operations and fragments into the request
251
+ request.instance_variable_set(:@operations, operations)
252
+ request.instance_variable_set(:@fragments, fragments)
184
253
  end
185
254
 
186
255
  protected
187
256
 
188
- # Clean and enable the collecting of listeners
189
- def release_listeners!
190
- @listeners = @base_listeners.dup
191
- end
257
+ # Execute a given block for each defined operation
258
+ def for_each_operation
259
+ operations.each do |key, value|
260
+ operations[key] = Component::Operation.build(request, value) \
261
+ if value.is_a?(::GQLParser::Token)
192
262
 
193
- # Disable the collecting of listeners
194
- def lock_listeners!
195
- listeners.freeze
263
+ yield(operations[key])
264
+ end
196
265
  end
197
266
 
198
- # A shortcut for +release_listeners!+ and +lock_listeners!+
267
+ # A start of the organize step
199
268
  def collect_listeners
200
- release_listeners!
269
+ return if defined?(@organized)
270
+ @stage = :organize
201
271
  yield
202
- lock_listeners!
203
272
  end
204
273
 
205
274
  # This is where the strategy is most effective. By preparing the tree,
206
275
  # it can load data in a pretty smart way
207
- def collect_data
276
+ def collect_data(force = false)
277
+ @stage = :prepare
208
278
  @data_pool = {}
209
279
  @context = request.build(Request::Context)
210
280
 
211
281
  # TODO: Create an orchestrator to allow cross query loading
212
- yield if listening_to?(:prepare)
282
+ yield if force || listening_to?(:prepare)
213
283
  end
214
284
 
215
- # Initiate the response context, named
216
- # ({Request::Context}[rdoc-ref:Rails::GraphQL::Request::Context])
217
- # and start collecting results
285
+ # Start collecting results
218
286
  def collect_response
287
+ @stage = :resolve
219
288
  yield
220
- ensure
221
- @context = @objects_pool = @data_pool = @listeners = nil
222
289
  end
223
290
 
224
291
  # Fetch the data for a given field and set as the first element
@@ -232,17 +299,6 @@ module Rails # :nodoc:
232
299
 
233
300
  result << current[key] if current.respond_to?(:key?) && current.key?(key)
234
301
  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
302
  end
247
303
  end
248
304
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module GraphQL
5
+ class Request
6
+ # = GraphQL Request Subscription
7
+ #
8
+ # A simple object to store information about a generated subscription
9
+ # TODO: Add a callback for the schema allowing it to prepare the context
10
+ # before saving it into a subscription
11
+ class Subscription
12
+ NULL_SCOPE = Object.new.freeze
13
+
14
+ attr_reader :sid, :schema, :args, :field, :scope, :context, :broadcastable,
15
+ :origin, :last_updated_at, :operation_id
16
+
17
+ alias broadcastable? broadcastable
18
+
19
+ def initialize(request, operation)
20
+ entrypoint = operation.selection.each_value.first
21
+
22
+ @schema = request.schema.namespace
23
+ @origin = request.origin
24
+ @operation_id = operation.hash
25
+ @args = entrypoint.arguments.to_h
26
+ @field = entrypoint.field
27
+ @context = request.context.to_h
28
+ @broadcastable = operation.broadcastable?
29
+ updated!
30
+
31
+ @scope = parse_scope(field.full_scope, request, operation)
32
+ @sid = request.schema.subscription_id_for(self)
33
+ end
34
+
35
+ def updated!
36
+ @last_updated_at = Time.current
37
+ end
38
+
39
+ def marshal_dump
40
+ raise ::TypeError, +"Request subscriptions must not be used as payload."
41
+ end
42
+
43
+ def inspect
44
+ (+<<~INFO).squish << '>'
45
+ #<#{self.class.name}
46
+ #{schema}@#{sid}
47
+ [#{scope.nil? ? scope.inspect : scope.hash}, #{args.hash}]
48
+ #{field.inspect}
49
+ INFO
50
+ end
51
+
52
+ protected
53
+
54
+ def parse_scope(list, request, operation)
55
+ enum = GraphQL.enumerate(list)
56
+ return NULL_SCOPE if enum.empty?
57
+
58
+ ext_self = nil
59
+ enum.map do |item|
60
+ case item
61
+ when Symbol
62
+ context[item]
63
+ when Proc
64
+ next item.call if item.arity == 0
65
+
66
+ ext_self ||= SimpleDelegator.new(self).tap do |object|
67
+ object.define_singleton_method(:request) { request }
68
+ object.define_singleton_method(:operation) { operation }
69
+ end
70
+
71
+ item.call(ext_self)
72
+ else
73
+ item
74
+ end
75
+ end.compact
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end