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,29 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Rails # :nodoc:
4
- module GraphQL # :nodoc:
3
+ module Rails
4
+ module GraphQL
5
5
  # = GraphQL Source
6
6
  #
7
7
  # Source is an abstract object that can contains fields, objects, and
8
- # informations that them are delivered to the relative schemas throughout
9
- # proxies, ensuring that it still kepps the main ownership of the objects
8
+ # information that them are delivered to the relative schemas throughout
9
+ # proxies, ensuring that it still keeps the main ownership of the objects
10
10
  class Source
11
11
  extend ActiveSupport::Autoload
12
12
 
13
13
  extend Helpers::InheritedCollection
14
- extend Helpers::WithSchemaFields
15
- extend Helpers::WithAssignment
16
14
  extend Helpers::WithNamespace
15
+ extend Helpers::WithEvents
16
+ extend Helpers::WithCallbacks
17
17
 
18
- DEFAULT_NAMESPACES = %i[base].freeze
18
+ include Helpers::Instantiable
19
19
 
20
- eager_autoload do
21
- autoload :ScopedArguments
22
-
23
- autoload :ActiveRecordSource
20
+ ATTACH_FIELDS_STEP = -> do
21
+ if fields?
22
+ attach_fields!(type, fields)
23
+ attach_scoped_arguments_to(fields.values)
24
+ end
24
25
  end
25
26
 
26
- ScopedConfig = Struct.new(:receiver, :self_object) do # :nodoc: all
27
+ autoload :Base
28
+ autoload :Builder
29
+
30
+ autoload :ScopedArguments
31
+ autoload :ActiveRecordSource
32
+
33
+ extend Source::Builder
34
+
35
+ # Helper class to be used as the +self+ in configuration blocks
36
+ ScopedConfig = Struct.new(:receiver, :self_object, :type) do
37
+ def safe_field(name, *args, **xargs, &block)
38
+ return if receiver.send(:skip_field?, name, on: type)
39
+ self_object.safe_field(name, *args, **xargs, &block)
40
+ end
41
+
42
+ # skip_field?(item.name, on: holder.kind)
27
43
  def respond_to_missing?(method_name, include_private = false)
28
44
  self_object.respond_to?(method_name, include_private) ||
29
45
  receiver.respond_to?(method_name, include_private)
@@ -39,53 +55,34 @@ module Rails # :nodoc:
39
55
  # If a source is marked as abstract, it means that it generates a new
40
56
  # source describer and any non-abstract class inherited from it will be
41
57
  # described by this new abstraction
42
- class_attribute :abstract, instance_writer: false, default: false
58
+ class_attribute :abstract, instance_accessor: false, default: false
43
59
 
44
60
  # List of hook names used while describing a new source. This basically
45
61
  # set the order of the execution of the hooks while validating the hooks
46
- # callbacks using the +on+ method. Make sure to kepp the +finish+ hook
47
- # always at the end of the list
48
- class_attribute :hook_names, instance_writer: false,
49
- default: %i[start object input query mutation finish].to_set
62
+ # callbacks using the +on+ method
63
+ class_attribute :hook_names, instance_accessor: false,
64
+ default: %i[start object input query mutation subscription].to_set
50
65
 
51
66
  # The list of hooks defined in order to describe a source
52
67
  inherited_collection :hooks, instance_reader: false, type: :hash_array
53
68
 
54
- # The name of the class (or the class itself) to be used as superclass for
55
- # the generate GraphQL object type of this source
56
- class_attribute :object_class, instance_writer: false,
57
- default: '::Rails::GraphQL::Type::Object'
58
-
59
- # The name of the class (or the class itself) to be used as superclass for
60
- # the generate GraphQL input type of this source
61
- class_attribute :input_class, instance_writer: false,
62
- default: '::Rails::GraphQL::Type::Input'
63
-
64
- # Mark if the objects created from this source will build fields for
65
- # associations associated to the object
66
- class_attribute :with_associations, instance_writer: false, default: true
67
-
68
69
  # A list of fields to skip when performing shared methods
69
- inherited_collection :skip_fields, instance_reader: false
70
+ inherited_collection :skip_fields, instance_reader: false, type: :set
70
71
 
71
72
  # A list of fields to skip but segmented by holder source
72
73
  inherited_collection :segmented_skip_fields, instance_reader: false, type: :hash_set
73
74
 
74
- # The purpose of instantiating a source is to have access to its public
75
- # methods. It then runs from the strategy perspective, pointing out any
76
- # other methods to the manually set event
77
- delegate_missing_to :event
78
- attr_reader :event
75
+ # A list of fields that should only be included. Available only when using
76
+ # individual builders
77
+ inherited_collection :segmented_only_fields, instance_reader: false, type: :hash_set
79
78
 
80
79
  self.abstract = true
81
80
 
82
81
  class << self
83
- attr_reader :schemas
82
+ delegate :field, :proxy_field, :overwrite_field, :field?, :field_names,
83
+ :gql_name, to: :object
84
84
 
85
- delegate :field, :proxy_field, :overwrite_field, :[], :field?,
86
- :field_names, :gql_name, to: :object
87
-
88
- def kind # :nodoc:
85
+ def kind
89
86
  :source
90
87
  end
91
88
 
@@ -96,27 +93,19 @@ module Rails # :nodoc:
96
93
 
97
94
  # Get the main name of the source
98
95
  def base_name
99
- name.demodulize[0..-7] unless abstract?
100
- end
101
-
102
- # Wait the end of the class in order to create the objects
103
- def inherited(subclass)
104
- subclass.abstract = false
105
- super if defined? super
106
-
107
- pending[subclass] ||= caller(1).find do |item|
108
- !item.end_with?("`inherited'")
109
- end
96
+ name.demodulize[0..-7]
110
97
  end
111
98
 
99
+ # :singleton-method:
112
100
  # Find a source for a given object. If none is found, then raise an
113
101
  # exception
114
102
  def find_for!(object)
115
- find_for(object) || raise(::ArgumentError, <<~MSG.squish)
103
+ find_for(object) || raise(::ArgumentError, (+<<~MSG).squish)
116
104
  Unable to find a source for "#{object.name}".
117
105
  MSG
118
106
  end
119
107
 
108
+ # :singleton-method:
120
109
  # Using the list of +base_sources+, find the first one that can handle
121
110
  # the given +object+
122
111
  def find_for(object)
@@ -124,60 +113,17 @@ module Rails # :nodoc:
124
113
  base_sources.reverse_each.find { |source| object <= source.assigned_class }
125
114
  end
126
115
 
127
- # Return the GraphQL object type associated with the source. It will
128
- # create one if it's not defined yet. The created class will be added
129
- # to the +::GraphQL+ namespace with the addition of any namespace of the
130
- # currect class
131
- def object
132
- @object ||= create_type(superclass: object_class)
133
- end
134
-
135
- # Return the GraphQL input type associated with the source. It will
136
- # create one if it's not defined yet. The created class will be added
137
- # to the +::GraphQL+ namespace with the addition of any namespace of the
138
- # currect class
139
- def input
140
- @input ||= create_type(superclass: input_class)
141
- end
142
-
143
- # Check if the object was already built
144
- def built?
145
- defined?(@built) && !!@built
146
- end
147
-
148
- # Checks if a given method can act as resolver
149
- def gql_resolver?(method_name)
150
- (instance_methods - GraphQL::Source.instance_methods).include?(method_name)
151
- end
152
-
153
116
  # Attach all defined schema fields into the schemas using the namespaces
154
117
  # configured for the source
155
- def attach_fields!
156
- refresh_schemas!
157
- schemas.each_value do |schema|
158
- Helpers::WithSchemaFields::SCHEMA_FIELD_TYPES.keys.each do |type|
159
- list = public_send("#{type}_fields")
160
- next if list.empty?
161
-
162
- list.each_value do |field|
163
- next if schema.has_field?(type, field)
164
- schema.add_proxy_field(type, field)
165
- end
166
- end
167
- end
118
+ def attach_fields!(type = :all, from = self)
119
+ schemas.each { |schema| schema.import_into(type, from) }
168
120
  end
169
121
 
170
122
  # Find all the schemas associated with the configured namespaces
171
- def refresh_schemas!
172
- @schemas = (namespaces.presence || DEFAULT_NAMESPACES).map do |ns|
173
- (schema = Schema.find(ns)).present? ? [ns, schema] : nil
174
- end.compact.to_h
175
- end
176
-
177
- def eager_load! # :nodoc:
178
- super
179
-
180
- build_pending!
123
+ def schemas
124
+ GraphQL.enumerate(namespaces.presence || :base).lazy.filter_map do |ns|
125
+ Schema.find(ns)
126
+ end
181
127
  end
182
128
 
183
129
  protected
@@ -191,54 +137,15 @@ module Rails # :nodoc:
191
137
  end
192
138
 
193
139
  # A little bypass to the actual type map after register method which
194
- # just add the namesapace by default
140
+ # just add the namespace by default
195
141
  # See {TypeMap#after_register}[rdoc-ref:Rails::GraphQL::TypeMap#after_register]
196
142
  def type_map_after_register(*args, **xargs, &block)
197
143
  xargs[:namespaces] ||= namespaces
198
144
  GraphQL.type_map.after_register(*args, **xargs, &block)
199
145
  end
200
146
 
201
- # A helper method to create an enum type
202
- def create_enum(enum_name, values, **xargs, &block)
203
- enumerator = values.each_pair if values.respond_to?(:each_pair)
204
- enumerator ||= values.each.with_index
205
-
206
- xargs = xargs.reverse_merge(once: true)
207
- create_type(:enum, as: enum_name.classify, **xargs) do
208
- indexed! if enumerator.first.last.is_a?(Numeric)
209
- enumerator.sort_by(&:last).map(&:first).each(&method(:add))
210
- instance_exec(&block) if block.present?
211
- end
212
- end
213
-
214
- # Helper method to create a class based on the given type and allows
215
- # several other settings to be executed on it
216
- def create_type(type = nil, **xargs, &block)
217
- name = "#{gql_module.name}::#{xargs.delete(:as) || base_name}"
218
- superclass = xargs.delete(:superclass)
219
- with_owner = xargs.delete(:with_owner)
220
-
221
- if superclass.nil?
222
- superclass = type.to_s.classify
223
- elsif superclass.is_a?(String)
224
- superclass = superclass.constantize
225
- end
226
-
227
- source = self
228
- Schema.send(:create_type, name, superclass, **xargs) do
229
- include Helpers::WithOwner if with_owner
230
- set_namespaces(*source.namespaces)
231
-
232
- self.owner = source if respond_to?(:owner=)
233
- self.assigned_to = source.safe_assigned_class \
234
- if source.assigned? && is_a?(Helpers::WithAssignment)
235
-
236
- instance_exec(&block) if block.present?
237
- end
238
- end
239
-
240
147
  # Add fields to be skipped on the given +source+ as the segment
241
- def skip_on(source, *fields)
148
+ def skip_from(source, *fields)
242
149
  segmented_skip_fields[source] += fields.flatten.compact.map(&:to_sym).to_set
243
150
  end
244
151
 
@@ -246,13 +153,13 @@ module Rails # :nodoc:
246
153
  # parent hooks. If the class is already built, then execute the hook.
247
154
  # Use the +unshift: true+ to add the hook at the beginning of the
248
155
  # list, which will then be the last to run
249
- def on(hook_name, unshift: false, &block)
250
- raise ArgumentError, <<~MSG.squish unless hook_names.include?(hook_name.to_sym)
156
+ def step(hook_name, unshift: false, &block)
157
+ raise ArgumentError, (+<<~MSG).squish unless hook_names.include?(hook_name.to_sym)
251
158
  The #{hook_name.inspect} is not a valid hook method.
252
159
  MSG
253
160
 
254
- if built?
255
- send("run_#{hook_name}_hooks", block)
161
+ if built?(hook_name)
162
+ hook_scope_for(hook_name).instance_exec(&block)
256
163
  else
257
164
  hooks[hook_name.to_sym].public_send(unshift ? :unshift : :push, block)
258
165
  end
@@ -262,7 +169,7 @@ module Rails # :nodoc:
262
169
  def skip(*names)
263
170
  names.each do |hook_name|
264
171
  hook_name = hook_name.to_s.singularize.to_sym
265
- on(hook_name) { throw :skip }
172
+ step(hook_name) { throw :skip }
266
173
  end
267
174
  end
268
175
 
@@ -270,7 +177,7 @@ module Rails # :nodoc:
270
177
  # +on hook_name do; end+
271
178
  def override(hook_name, &block)
272
179
  skip(hook_name)
273
- on(hook_name, &block)
180
+ step(hook_name, &block)
274
181
  end
275
182
 
276
183
  # It's an alternative to +self.hook_names -= %i[*names]+ which
@@ -291,86 +198,55 @@ module Rails # :nodoc:
291
198
 
292
199
  # Return the module where the GraphQL types should be created at
293
200
  def gql_module
294
- name.starts_with?('GraphQL::') ? module_parent : ::GraphQL
201
+ name.start_with?('GraphQL::') ? module_parent : ::GraphQL
295
202
  end
296
203
 
297
- # Get the list of fields to be skipped from the given +holder+ as the
298
- # segment source
299
- def skips_for(holder)
300
- segment = holder.kind
301
- segment = :input if segment.eql?(:input_object)
302
- segmented = all_segmented_skip_fields[segment]
303
- segmented.present? ? all_skip_fields + segmented : all_skip_fields
204
+ # Add one or more fields to the list of fields that needs to be
205
+ # ignored in all places. It converts strings to underscore
206
+ def skip_fields!(*list)
207
+ list = list.flatten.map do |value|
208
+ value.is_a?(Symbol) ? value.to_s : value.to_s.underscore
209
+ end
210
+
211
+ self.skip_fields.merge(list)
304
212
  end
305
213
 
306
- private
214
+ # Check if a given field +name+ should be skipped on the give type
215
+ def skip_field?(name, on:)
216
+ on = :input if on == :input_object
217
+ name = name.to_s.underscore
307
218
 
308
- # The list of pending sources to be built asscoaited to where they
309
- # were defined
310
- def pending
311
- @@pending ||= {}
219
+ all_skip_fields&.include?(name) ||
220
+ all_segmented_skip_fields.try(:[], on)&.include?(name) ||
221
+ all_segmented_only_fields.try(:[], on)&.exclude?(name)
312
222
  end
313
223
 
314
- # Check if there are pending sources to be built
315
- def pending?
316
- pending.any?
224
+ # Run a list of hooks using the +source+ as the instance of the block
225
+ def run_hooks(hook_name, source = self)
226
+ all_hooks.try(:[], hook_name.to_sym)&.reverse_each do |block|
227
+ source.instance_exec(&block)
228
+ end
317
229
  end
318
230
 
319
- # Build the pending sources
320
- def build_pending!
321
- while (klass, = pending.shift)
322
- klass.send(:build!) unless klass.abstract?
323
- end
231
+ private
232
+
233
+ # Make sure to reset the value of +abstract+
234
+ def inherited(subclass)
235
+ subclass.abstract = false
236
+ super if defined? super
324
237
  end
325
238
 
326
239
  # Find all classes that inherits from source that are abstract,
327
240
  # meaning that they are a base sources
328
241
  def base_sources
329
- @@base_sources ||= begin
330
- eager_load!
331
- descendants.select(&:abstract?).to_set
332
- end
333
- end
334
-
335
- # Build all the objects associated with this source
336
- def build!
337
- return if built?
338
-
339
- raise DefinitionError, <<~MSG.squish if abstract
340
- Abstract source #{name} cannot be built.
341
- MSG
342
-
343
- @built = true
344
-
345
- catch(:done) do
346
- hook_names.each do |hook_name|
347
- break if hook_name === :finish
348
- catch(:skip) { send("run_#{hook_name}_hooks") }
349
- end
350
- end
351
-
352
- catch(:skip) { send(:run_finish_hooks) } if respond_to?(:run_finish_hooks, true)
242
+ @@base_sources ||= GraphQL.config.sources.map(&:constantize).to_set
353
243
  end
354
244
 
355
- {
356
- start: 'self',
357
- finish: 'self',
358
- object: 'Helpers::AttributeDelegator.new(self, :object)',
359
- input: 'Helpers::AttributeDelegator.new(self, :input)',
360
- query: format('schema_scoped_config(self, %s)', ':query'),
361
- mutation: format('schema_scoped_config(self, %s)', ':mutation'),
362
- subscription: format('schema_scoped_config(self, %s)', ':subscription'),
363
- }.each do |key, object|
364
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
365
- def run_#{key}_hooks(list = nil)
366
- source_config = Source::ScopedConfig.new(self, #{object})
367
- Array.wrap(list.presence || all_hooks[:#{key}]).reverse_each do |block|
368
- source_config.instance_exec(&block)
369
- end
370
- end
371
- RUBY
372
- end
373
245
  end
246
+
247
+ step(:query, &ATTACH_FIELDS_STEP)
248
+ step(:mutation, &ATTACH_FIELDS_STEP)
249
+ step(:subscription, &ATTACH_FIELDS_STEP)
374
250
  end
375
251
  end
376
252
  end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_cable'
4
+
5
+ module Rails
6
+ module GraphQL
7
+ module Subscription
8
+ module Provider
9
+ # = GraphQL Action Cable Subscription Provider
10
+ #
11
+ # The subscription provider associated with Rails Action Cable, that
12
+ # delivers subscription notifications through an Action Cable Channel
13
+ class ActionCable < Base
14
+ INTERNAL_CHANNEL = 'rails-graphql:events'
15
+
16
+ attr_reader :cable, :prefix
17
+
18
+ def initialize(*args, **options)
19
+ @cable = options.fetch(:cable, ::ActionCable)
20
+ @prefix = options.fetch(:prefix, 'graphql')
21
+
22
+ @event_callback = ->(message) do
23
+ method_name, args, xargs = Marshal.load(message)
24
+ @mutex.synchronize { send(method_name, *args, **xargs) }
25
+ end
26
+
27
+ super
28
+ end
29
+
30
+ def shutdown
31
+ @pubsub = nil
32
+ end
33
+
34
+ def accepts?(operation)
35
+ operation.request.origin.is_a?(::ActionCable::Channel::Base)
36
+ end
37
+
38
+ def add(*subscriptions)
39
+ with_pubsub do
40
+ subscriptions.each do |item|
41
+ log(:added, item)
42
+ store.add(item)
43
+ stream_from(item)
44
+ end
45
+ end
46
+ end
47
+
48
+ def async_remove(item)
49
+ return unless instance?(item) || !(item = store.fetch(item)).nil?
50
+ cable.server.broadcast(stream_name(item), unsubscribed_payload)
51
+ store.remove(item)
52
+
53
+ log(:removed, item)
54
+ end
55
+
56
+ def async_update(item, data = nil)
57
+ return unless instance?(item) || !(item = store.fetch(item)).nil?
58
+ removing = false
59
+
60
+ log(:updated, item) do
61
+ data = execute(item) if data.nil?
62
+ item.updated!
63
+
64
+ unless (removing = unsubscribing?(data))
65
+ data = { 'result' => data, 'more' => true }
66
+ cable.server.broadcast(stream_name(item), data)
67
+ end
68
+ end
69
+
70
+ async_remove(item) if removing
71
+ end
72
+
73
+ def stream_name(item)
74
+ "#{prefix}:#{instance?(item) ? item.sid : item}"
75
+ end
76
+
77
+ protected
78
+
79
+ def stream_from(item)
80
+ item.origin.stream_from(stream_name(item))
81
+ end
82
+
83
+ def execute(subscription, **xargs)
84
+ super(subscription, origin: subscription.origin, **xargs, as: :hash)
85
+ end
86
+
87
+ def async_exec(method_name, *args, **xargs)
88
+ payload = [method_name, args, store.serialize(**xargs)]
89
+ with_pubsub { @pubsub.broadcast(INTERNAL_CHANNEL, Marshal.dump(payload)) }
90
+ nil
91
+ end
92
+
93
+ def with_pubsub(&callback)
94
+ success = -> { cable.server.event_loop.post(&callback) }
95
+ return success.call if defined?(@pubsub) && !@pubsub.nil?
96
+
97
+ cable.server.pubsub.subscribe(INTERNAL_CHANNEL, @event_callback, success)
98
+ @pubsub = cable.server.pubsub
99
+ end
100
+
101
+ def validate!
102
+ super
103
+
104
+ raise ValidationError, (+<<~MSG).squish if @prefix.blank?
105
+ Unable to setup #{self.class.name} because a proper prefix was not provided.
106
+ MSG
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end