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
@@ -1,14 +1,174 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Rails # :nodoc:
4
- module GraphQL # :nodoc:
5
- class Request # :nodoc:
6
- class Component # :nodoc:
3
+ module Rails
4
+ module GraphQL
5
+ class Request
6
+ class Component
7
7
  # = GraphQL Request Component Subscription Operation
8
8
  #
9
9
  # Handles a subscription operation inside a request.
10
10
  class Operation::Subscription < Operation
11
+ UNSUBSCRIBED_PAYLOAD = { 'more' => false }.freeze
12
+ UNSUBSCRIBED_RESULT = Object.new
13
+
11
14
  redefine_singleton_method(:subscription?) { true }
15
+
16
+ def initialize(*)
17
+ @initial = true
18
+
19
+ super
20
+ end
21
+
22
+ # Fetch the subscription only when necessary
23
+ def subscription
24
+ return unless defined?(@subscription)
25
+ return @subscription if @subscription.is_a?(Request::Subscription)
26
+ @subscription = schema.subscription_provider.fetch(@subscription)
27
+ end
28
+
29
+ # Check if the operation is running in its first iteration, which will
30
+ # produce a subscription to the field
31
+ def subscribing?
32
+ @initial
33
+ end
34
+
35
+ # Check if the current operation is running under broadcasting mode
36
+ def broadcasting?
37
+ request.context.broadcasting == true
38
+ end
39
+
40
+ # Check if the operation can be broadcasted
41
+ def broadcastable?
42
+ return @broadcastable if defined?(@broadcastable)
43
+ @broadcastable = selection.each_value.all?(&:broadcastable?)
44
+ end
45
+
46
+ # Either throw back an empty result or skip the operation
47
+ def no_update!
48
+ subscribing? ? skip! : throw(:skip_subscription_update, EMPTY_HASH)
49
+ end
50
+
51
+ # If unsubscribe is called during an update, skip and return a proper
52
+ # result
53
+ def unsubscribe!
54
+ if subscribing?
55
+ schema.remove_subscriptions(subscription.sid) if subscription.present?
56
+ else
57
+ throw(:skip_subscription_update, UNSUBSCRIBED_RESULT)
58
+ end
59
+ end
60
+
61
+ # Build the cache object
62
+ def cache_dump(initial = true)
63
+ hash = super()
64
+ hash[:initial] = initial
65
+ hash[:broadcastable] = broadcastable?
66
+
67
+ unless initial
68
+ hash[:sid] = @subscription&.sid
69
+ hash[:variables] = @variables
70
+ end
71
+
72
+ hash
73
+ end
74
+
75
+ # Organize from cache data
76
+ def cache_load(data)
77
+ @initial = data[:initial]
78
+ @broadcastable = data[:broadcastable]
79
+
80
+ unless subscribing?
81
+ @variables = data[:variables]
82
+ @subscription = data[:sid]
83
+ end
84
+
85
+ super
86
+ end
87
+
88
+ protected
89
+
90
+ # Rewrite this method so that the subscription can be generated in
91
+ # the right place
92
+ def resolve_then(after_block = nil, &block)
93
+ subscribe_block = -> do
94
+ save_subscription
95
+ trigger_event(:subscribed)
96
+ after_block.call if after_block.present?
97
+ end
98
+
99
+ super(subscribe_block, &block)
100
+ end
101
+
102
+ # Save the subscription using the schema subscription provider
103
+ def save_subscription
104
+ return unless subscribing? && !(invalid? ||skipped?)
105
+ check_invalid_subscription!
106
+
107
+ @subscription = Request::Subscription.new(request, self)
108
+ request.write_cache_request(subscription.sid, build_subscription_cache)
109
+ schema.add_subscriptions(subscription)
110
+
111
+ request.subscriptions[subscription.sid] = subscription
112
+ rescue => e
113
+ if subscription.present?
114
+ schema.delete_from_cache(subscription.sid)
115
+ schema.remove_subscriptions(subscription.sid)
116
+ end
117
+
118
+ raise SubscriptionError, (+<<~MSG).squish
119
+ Unable to proper setup a subscription on #{display_name}: #{e.message}
120
+ MSG
121
+ end
122
+
123
+ # A subscription is invalid if more than one field was requested or
124
+ # if the only thing in the selection is a spread
125
+ def check_invalid_subscription!
126
+ raise ValidationError, (+<<~MSG).squish if @selection.size != 1
127
+ It has #{@selection.size} and must have 1 single field on it.
128
+ MSG
129
+
130
+ element = @selection.each_value.first
131
+ raise ValidationError, (+<<~MSG).squish unless element.kind == :field
132
+ The only element inside of it must be a field, #{element.kind} found.
133
+ MSG
134
+
135
+ raise ValidationError, (+<<~MSG).squish if element.is_a?(Component::Typename)
136
+ Unable to subscribe to a __typeName field.
137
+ MSG
138
+
139
+ raise ValidationError, (+<<~MSG).squish if element.unresolvable?
140
+ Field is unresolvable.
141
+ MSG
142
+
143
+ raise ValidationError, (+<<~MSG).squish unless schema.accepts_subscription?(self)
144
+ Operation unaccepted.
145
+ MSG
146
+ end
147
+
148
+ # Very similar to the +cache_dump+ from the request, however this
149
+ # will chop the parts exclusively for the subscription
150
+ def build_subscription_cache
151
+ # Prepare each used fragment to be set on cache and on the document
152
+ frag_nodes = []
153
+ fragments = used_fragments.empty? ? nil : begin
154
+ request.fragments.slice(*used_fragments).transform_values do |frag|
155
+ frag_nodes << frag.instance_variable_get(:@node)
156
+ frag.try(:cache_dump)
157
+ end.compact
158
+ end
159
+
160
+ # Return the cache result
161
+ {
162
+ strategy: { class: request.strategy.class },
163
+ operation_name: request.operation_name,
164
+ type_map_version: request.schema.version,
165
+ document: [[@node], frag_nodes.presence],
166
+ errors: request.errors.cache_dump,
167
+ operations: { name => cache_dump(false) },
168
+ fragments: fragments,
169
+ }
170
+ end
171
+
12
172
  end
13
173
  end
14
174
  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 Component Operation
7
7
  #
8
8
  # This class holds information about a given operation. This will guide
@@ -16,9 +16,9 @@ module Rails # :nodoc:
16
16
  class << self
17
17
  alias type kind
18
18
 
19
- # Helper method to initialize an operation given the data
20
- def build(request, node, data)
21
- request.build(const_get(data[:kind].classify), request, node, data)
19
+ # Helper method to initialize an operation given the node
20
+ def build(request, node)
21
+ request.build(const_get(node.type.to_s.classify), request, node)
22
22
  end
23
23
 
24
24
  # Rewrite the kind to always return +:operation+
@@ -49,6 +49,7 @@ module Rails # :nodoc:
49
49
  autoload :Subscription
50
50
 
51
51
  delegate :type, :query?, :mutation?, :subscription?, to: :class
52
+ delegate :schema, to: :strategy
52
53
 
53
54
  attr_reader :name, :variables, :arguments, :request
54
55
 
@@ -57,11 +58,11 @@ module Rails # :nodoc:
57
58
  alias vars variables
58
59
  alias all_arguments arguments
59
60
 
60
- def initialize(request, node, data)
61
- @name = data[:name]
61
+ def initialize(request, node)
62
+ @name = node[1]
62
63
  @request = request
63
64
 
64
- super(node, data)
65
+ super(node)
65
66
 
66
67
  check_invalid_operation!
67
68
  end
@@ -88,14 +89,40 @@ module Rails # :nodoc:
88
89
  response.safe_add(name, nil) if stacked_selection?
89
90
  end
90
91
 
91
- # Stores all the used arguments to report not used ones
92
+ # Stores all the used variables to report not used ones
92
93
  def used_variables
93
94
  @used_variables ||= Set.new
94
95
  end
95
96
 
97
+ # Stores all the used fragments
98
+ def used_fragments
99
+ @used_fragments ||= Set.new
100
+ end
101
+
96
102
  # A fast way to access the correct display name for log or errors
97
103
  def log_source
98
- @log_source ||= name.blank? ? type : "#{name} #{type}"
104
+ @log_source ||= name.blank? ? type : +"#{name} #{type}"
105
+ end
106
+
107
+ # The hash of operations must take into consideration the used fragments
108
+ def hash
109
+ return super unless defined?(@used_fragments)
110
+
111
+ super ^ used_fragments.reduce(0) do |value, fragment|
112
+ request.fragments[fragment].hash
113
+ end
114
+ end
115
+
116
+ # Build the cache object
117
+ def cache_dump
118
+ super.merge(type: self.class)
119
+ end
120
+
121
+ # Organize from cache data
122
+ def cache_load(data)
123
+ @name = data[:node][1]
124
+
125
+ super
99
126
  end
100
127
 
101
128
  protected
@@ -113,9 +140,9 @@ module Rails # :nodoc:
113
140
  # Perform the organization step
114
141
  def organize_then(&block)
115
142
  super(block) do
116
- parse_variables
117
- parse_directives(type)
118
- parse_selection
143
+ parse_variables(@node[2])
144
+ parse_directives(@node[3], type)
145
+ parse_selection(@node[4])
119
146
  end
120
147
  end
121
148
 
@@ -133,42 +160,47 @@ module Rails # :nodoc:
133
160
 
134
161
  # Name used for debug purposes
135
162
  def display_name
136
- @display_name ||= "#{type.to_s.titlecase} #{name.presence || '__default__'}"
163
+ @display_name ||= +"#{type.to_s.titlecase} #{name.presence || '__default__'}"
137
164
  end
138
165
 
139
- # Add an error for each not used variable and then clean up some data
166
+ # Add an error for each not used variable
140
167
  def report_unused_variables
168
+ return if arguments.nil?
169
+
141
170
  (arguments.keys - used_variables.to_a).each do |key|
142
171
  argument = arguments[key]
143
- request.report_node_error(<<~MSG.squish, argument.node || @node)
144
- Unused variable $#{argument.gql_name} on #{log_source}.
172
+ request.report_node_error((+<<~MSG).squish, argument.node)
173
+ Variable $#{argument.gql_name} was provided to #{log_source} but not used.
145
174
  MSG
146
175
  end
147
176
 
148
- @arguments = nil
177
+ # Report all used variables to the request for greater scope
178
+ request.instance_variable_get(:@used_variables).merge(arguments.keys)
179
+
180
+ # Clear anything that is not necessary anymore
149
181
  @used_variables = nil
150
182
  end
151
183
 
152
184
  # If there is another operation with the same name already defined,
153
185
  # raise an error. If an anonymous was started, then any other
154
- # operatios is invalid.
186
+ # operations is invalid.
155
187
  def check_invalid_operation!
156
- if request.operations.key?(nil)
157
- invalidate!
188
+ other = request.document[0].find do |other|
189
+ other != @node && name == other[1]
190
+ end
191
+
192
+ return if other.nil?
193
+ invalidate!
158
194
 
159
- request.report_node_error(<<~MSG.squish, @node)
195
+ if name.nil?
196
+ request.report_node_error((+<<~MSG).squish, self)
160
197
  Unable to process the operation #{display_name} when the document
161
198
  contain multiple anonymous operations.
162
199
  MSG
163
- elsif request.operations.key?(name)
164
- invalidate!
165
-
166
- other_node = request.operations[name].instance_variable_get(:@node)
167
- location = GraphQL::Native.get_location(other_node)
168
-
169
- request.report_node_error(<<~MSG.squish, @node)
200
+ else
201
+ request.report_node_error((+<<~MSG).squish, self)
170
202
  Duplicated operation named "#{name}" defined on
171
- line #{location.begin_line}:#{location.begin_column}.
203
+ line #{other.begin_line}:#{other.begin_column}.
172
204
  MSG
173
205
  end
174
206
  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 Component Spread
7
7
  #
8
8
  # This class holds information about a given spread that should be
@@ -11,20 +11,18 @@ module Rails # :nodoc:
11
11
  include SelectionSet
12
12
  include Directives
13
13
 
14
- delegate :operation, :typename, to: :parent
14
+ delegate :operation, :typename, :request, to: :parent
15
15
  delegate :variables, to: :operation
16
16
 
17
- parent_memoize :request
17
+ attr_reader :name, :parent, :fragment, :current_object, :type_klass
18
18
 
19
- attr_reader :name, :parent, :fragment, :type_klass
20
-
21
- def initialize(parent, node, data)
19
+ def initialize(parent, node)
22
20
  @parent = parent
23
21
 
24
- @name = data[:name]
25
- @inline = data[:inline]
22
+ @name = node[0]
23
+ @inline = name.nil?
26
24
 
27
- super(node, data)
25
+ super(node)
28
26
  end
29
27
 
30
28
  # Check if the object is an inline spread
@@ -32,9 +30,14 @@ module Rails # :nodoc:
32
30
  @inline.present?
33
31
  end
34
32
 
33
+ # Check if all the sub fields or the fragment is broadcastable
34
+ def broadcastable?
35
+ inline? ? selection.each_value.all?(&:broadcastable?) : fragment.broadcastable?
36
+ end
37
+
35
38
  # Redirect to the fragment or check the inline type before resolving
36
39
  def resolve_with!(object)
37
- return if invalid?
40
+ return if unresolvable?
38
41
 
39
42
  @current_object = object
40
43
  resolve!
@@ -42,6 +45,25 @@ module Rails # :nodoc:
42
45
  @current_object = nil
43
46
  end
44
47
 
48
+ # Build the cache object
49
+ def cache_dump
50
+ inline? ? super.merge(type_klass: all_to_gid(type_klass)) : super
51
+ end
52
+
53
+ # Organize from cache data
54
+ def cache_load(data)
55
+ @name = data[:node][0]
56
+ @inline = name.nil?
57
+
58
+ if inline?
59
+ @type_klass = all_from_gid(data[:type_klass])
60
+ else
61
+ collect_fragment
62
+ end
63
+
64
+ super
65
+ end
66
+
45
67
  protected
46
68
 
47
69
  # Spread always resolve inline selection unstacked on response,
@@ -51,13 +73,13 @@ module Rails # :nodoc:
51
73
  end
52
74
 
53
75
  # Just provide the correct location for directives
54
- def parse_directives
55
- super(inline? ? :inline_fragment : :fragment_spread)
76
+ def parse_directives(nodes)
77
+ super(nodes, (inline? ? :inline_fragment : :fragment_spread))
56
78
  end
57
79
 
58
80
  # Scope the arguments whenever stacked within a spread
59
81
  def stacked(*)
60
- Request::Arguments.scoped(operation) { super }
82
+ Arguments.scoped(operation) { super }
61
83
  end
62
84
 
63
85
  # Normal mode of the organize step
@@ -66,7 +88,6 @@ module Rails # :nodoc:
66
88
  def organize
67
89
  inline? ? organize_then { organize_fields } : organize_then do
68
90
  run_on_fragment(:organize!)
69
- # Ensure necessary variables
70
91
  end
71
92
  end
72
93
 
@@ -74,28 +95,28 @@ module Rails # :nodoc:
74
95
  def organize_then(&block)
75
96
  super(block) do
76
97
  if inline?
77
- @type_klass = find_type!(data[:type])
78
- parse_selection
98
+ @type_klass = find_type!(@node[1])
99
+ parse_directives(@node[2])
100
+ parse_selection(@node[3])
79
101
  else
80
- @fragment = request.fragments[name]
81
- raise ArgumentError, <<~MSG.squish if @fragment.nil?
102
+ parse_directives(@node[2])
103
+ @fragment = collect_fragment
104
+ raise ArgumentError, (+<<~MSG).squish if @fragment.nil?
82
105
  The "#{name}" fragment is not defined in this request.
83
106
  MSG
84
107
  end
85
-
86
- parse_directives
87
108
  end
88
109
  end
89
110
 
90
111
  # Spread has a special behavior when using a fragment
91
112
  def prepare
92
113
  return super if inline?
93
- raise 'Prepare with fragment not implemented yet'
114
+ raise(+'Prepare with fragment not implemented yet')
94
115
  end
95
116
 
96
117
  # Resolve the spread operation
97
118
  def resolve
98
- return if invalid?
119
+ return if unresolvable?
99
120
 
100
121
  object = (defined?(@current_object) && @current_object) || parent.type_klass
101
122
  return run_on_fragment(:resolve_with!, object) unless inline?
@@ -111,7 +132,29 @@ module Rails # :nodoc:
111
132
  # Most of the things that are redirected to the fragment needs to run
112
133
  # inside a arguments scoped
113
134
  def run_on_fragment(method_name, *args)
114
- Request::Arguments.scoped(operation) { fragment.public_send(method_name, *args) }
135
+ Arguments.scoped(operation) { fragment.public_send(method_name, *args) }
136
+ end
137
+
138
+ # Only initialize the fragment once and only when requested for the
139
+ # first time. It also reports to the operation the used variables
140
+ # within the fragment
141
+ def collect_fragment
142
+ node = request.fragments[name]
143
+
144
+ if node.is_a?(Component::Fragment)
145
+ unless (used_variables = node.used_variables).nil?
146
+ operation.used_variables.merge(used_variables)
147
+ end
148
+
149
+ unless (used_fragments = node.used_fragments).nil?
150
+ operation.used_fragments.merge(used_fragments)
151
+ end
152
+
153
+ node
154
+ else
155
+ operation.used_fragments << name
156
+ request.fragments[name] = request.build(Component::Fragment, request, node)
157
+ end
115
158
  end
116
159
  end
117
160
  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 Component Typename
7
7
  #
8
8
  # Extra component which simulates a field that its only purpose is to
@@ -11,11 +11,9 @@ module Rails # :nodoc:
11
11
  include ValueWriters
12
12
  include Directives
13
13
 
14
- delegate :operation, to: :parent
14
+ delegate :operation, :request, to: :parent
15
15
  delegate :variables, to: :operation
16
16
 
17
- parent_memoize :request
18
-
19
17
  attr_reader :name, :alias_name, :parent
20
18
 
21
19
  # Rewrite the kind to always return +:field+
@@ -23,13 +21,13 @@ module Rails # :nodoc:
23
21
  :field
24
22
  end
25
23
 
26
- def initialize(parent, node, data)
24
+ def initialize(parent, node)
27
25
  @parent = parent
28
26
 
29
- @name = data[:name]
30
- @alias_name = data[:alias]
27
+ @name = node[0]
28
+ @alias_name = node[1]
31
29
 
32
- super(node, data)
30
+ super(node)
33
31
  end
34
32
 
35
33
  # Set the value that the field will be resolved as
@@ -50,6 +48,23 @@ module Rails # :nodoc:
50
48
  response.serialize(Type::Scalar::StringScalar, gql_name, value)
51
49
  end
52
50
 
51
+ # Typename is always broadcastable
52
+ def broadcastable?
53
+ true
54
+ end
55
+
56
+ # Prepare is not necessary for this field
57
+ def prepare!
58
+ end
59
+
60
+ # Organize from cache data
61
+ def cache_load(data)
62
+ @name = data[:node][0]
63
+ @alias_name = data[:node][1]
64
+
65
+ super
66
+ end
67
+
53
68
  protected
54
69
 
55
70
  # Normal mode of the organize step
@@ -59,14 +74,14 @@ module Rails # :nodoc:
59
74
 
60
75
  # Perform the organization step
61
76
  def organize_then(&block)
62
- super(block) { parse_directives(:field) }
77
+ super(block) { parse_directives(@node[3], :field) }
63
78
  end
64
79
 
65
80
  # Go through the right flow to write the value
66
81
  def resolve_then
67
82
  super do
68
83
  typename = @typename || parent.typename
69
- raise InvalidValueError, <<~MSG.squish if typename.blank?
84
+ raise InvalidValueError, (+<<~MSG).squish if typename.blank?
70
85
  The #{gql_name} field value cannot be null.
71
86
  MSG
72
87