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,7 +1,7 @@
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 Active Record
6
6
  #
7
7
  # This source allows the translation of active record objects into a new
@@ -10,20 +10,23 @@ module Rails # :nodoc:
10
10
  # 2. 1 Input
11
11
  # 3. 2 Query fields (ingular and plural)
12
12
  # 4. 3 Mutation fields (create, update, destroy)
13
- class Source::ActiveRecordSource < Source
13
+ class Source::ActiveRecordSource < Source::Base
14
14
  include Source::ScopedArguments
15
15
 
16
16
  require_relative 'active_record/builders'
17
17
  extend Builders
18
18
 
19
19
  validate_assignment('ActiveRecord::Base') do |value|
20
- "The \"#{value.name}\" is not a valid Active Record model"
20
+ +"The \"#{value.name}\" is not a valid Active Record model"
21
21
  end
22
22
 
23
+ # Mark if the objects created from this source will build fields for
24
+ # associations associated to the object
25
+ class_attribute :with_associations, instance_accessor: false, default: true
26
+
23
27
  # The name of the class (or the class itself) to be used as superclass for
24
28
  # the generate GraphQL interface type of this source
25
- class_attribute :interface_class, instance_writer: false
26
- alias interface interface_class
29
+ class_attribute :interface_class, instance_accessor: false
27
30
 
28
31
  %i[object interface input].each do |type|
29
32
  settings = { abstract: true, with_owner: true }
@@ -31,23 +34,22 @@ module Rails # :nodoc:
31
34
  end
32
35
 
33
36
  self.abstract = true
37
+ self.hook_names = hook_names.to_a.insert(1, :enums).to_set
34
38
 
35
- delegate :primary_key, :singular, :plural, :model, to: :class
39
+ delegate :primary_key, :singular, :plural, :model, :id_columns, to: :class
36
40
 
37
- skip_on :input, :created_at, :updated_at
41
+ skip_from(:input, :created_at, :updated_at)
38
42
 
39
- on :start do
40
- GraphQL.enable_ar_adapter(adapter_name)
41
- build_enum_types
42
- end
43
+ step(:start) { GraphQL.enable_ar_adapter(adapter_name) }
44
+ step(:enums) { build_enum_types }
43
45
 
44
- on :object do
46
+ step(:object) do
45
47
  build_attribute_fields(self)
46
48
  build_reflection_fields(self)
47
49
  end
48
50
 
49
- on :input do
50
- extra = { primary_key => { null: true } }
51
+ step(:input) do
52
+ extra = GraphQL.enumerate(primary_key).entries.product([{ null: true }]).to_h
51
53
  build_attribute_fields(self, **extra)
52
54
  build_reflection_inputs(self)
53
55
 
@@ -61,47 +63,39 @@ module Rails # :nodoc:
61
63
  end
62
64
  end
63
65
 
64
- on :query do
66
+ step(:query) do
67
+ build_object
68
+
65
69
  safe_field(plural, object, full: true) do
66
- before_resolve :load_records
70
+ before_resolve(:load_records)
67
71
  end
68
72
 
69
73
  safe_field(singular, object, null: false) do
70
- argument primary_key, :id, null: false
71
- before_resolve :load_record
74
+ build_primary_key_arguments(self)
75
+ before_resolve(:load_record)
72
76
  end
73
77
  end
74
78
 
75
- on :mutation do
79
+ step(:mutation) do
80
+ build_object
81
+ build_input
82
+
76
83
  safe_field("create_#{singular}", object, null: false) do
77
- argument singular, input, null: false
78
- perform :create_record
84
+ argument(singular, input, null: false)
85
+ perform(:create_record)
79
86
  end
80
87
 
81
88
  safe_field("update_#{singular}", object, null: false) do
82
- argument primary_key, :id, null: false
83
- argument singular, input, null: false
84
- before_resolve :load_record
85
- perform :update_record
89
+ build_primary_key_arguments(self)
90
+ argument(singular, input, null: false)
91
+ before_resolve(:load_record)
92
+ perform(:update_record)
86
93
  end
87
94
 
88
95
  safe_field("delete_#{singular}", :boolean, null: false) do
89
- argument primary_key, :id, null: false
90
- before_resolve :load_record
91
- perform :destroy_record
92
- end
93
- end
94
-
95
- on :finish do
96
- attach_fields!
97
- attach_scoped_arguments_to(query_fields.values)
98
- attach_scoped_arguments_to(mutation_fields.values)
99
-
100
- next if model.base_class == model
101
-
102
- # TODO: Allow nested inheritance for setting up implementation
103
- type_map_after_register(model.base_class) do |type|
104
- object.implements(type) if type.interface?
96
+ build_primary_key_arguments(self)
97
+ before_resolve(:load_record)
98
+ perform(:destroy_record)
105
99
  end
106
100
  end
107
101
 
@@ -110,10 +104,11 @@ module Rails # :nodoc:
110
104
  delegate :singular, :plural, :param_key, to: :model_name
111
105
  delegate :adapter_name, to: 'model.connection'
112
106
 
107
+ alias interface interface_class
113
108
  alias model assigned_class
114
109
  alias model= assigned_to=
115
110
 
116
- # Set the assignemnt to a model with a similar name as the source
111
+ # Set the assignment to a model with a similar name as the source
117
112
  def assigned_to
118
113
  @assigned_to ||= name.delete_prefix('GraphQL::')[0..-7]
119
114
  end
@@ -125,26 +120,38 @@ module Rails # :nodoc:
125
120
  end
126
121
 
127
122
  # Just a little override to ensure that both model and table are ready
128
- def build!
123
+ def build!(*)
129
124
  super if model&.table_exists?
130
125
  end
131
126
 
127
+ # Hook into the unregister to clean enums
128
+ def unregister!
129
+ super
130
+ @enums = nil
131
+ end
132
+
132
133
  protected
133
134
 
134
135
  # Check if a given +attr_name+ is associated with a presence validator
135
- # but ignores when there is a default value
136
+ # (that does not include +if+ nor +unless+), but ignores when there is
137
+ # a default value
136
138
  def attr_required?(attr_name)
137
139
  return true if attr_name.eql?(primary_key)
138
140
  return false if model.columns_hash[attr_name]&.default.present?
139
141
  return false unless model._validators.key?(attr_name.to_sym)
140
- model._validators[attr_name.to_sym].any?(presence_validator)
142
+
143
+ model._validators[attr_name.to_sym].any? do |validator|
144
+ validator.is_a?(presence_validator) &&
145
+ !(validator.options[:if] ||
146
+ validator.options[:unless])
147
+ end
141
148
  rescue ::ActiveRecord::StatementInvalid
142
149
  false
143
150
  end
144
151
 
145
152
  private
146
153
 
147
- def presence_validator # :nodoc:
154
+ def presence_validator
148
155
  ::ActiveRecord::Validations::PresenceValidator
149
156
  end
150
157
  end
@@ -155,8 +162,9 @@ module Rails # :nodoc:
155
162
  end
156
163
 
157
164
  # Prepare to load a single record from the underlying table
158
- def load_record(scope = model.default_scoped)
159
- scope.find(event.argument(primary_key))
165
+ def load_record(scope = model.default_scoped, find_by: nil)
166
+ find_by ||= { primary_key => event.argument(primary_key) }
167
+ inject_scopes(scope, :relation).find_by(find_by)
160
168
  end
161
169
 
162
170
  # Get the chain result and preload the records with thre resulting scope
@@ -212,23 +220,31 @@ module Rails # :nodoc:
212
220
 
213
221
  # Preload the records for a given +association+ using the current value.
214
222
  # It can be further specified with a given +scope+
223
+ # TODO: On Rails 7 we can use the Preloader::Branch class
215
224
  def preload(association, scope = nil)
216
225
  reflection = model._reflect_on_association(association)
217
226
  records = current_value.is_a?(preloader_association) \
218
227
  ? current_value.preloaded_records \
219
228
  : Array.wrap(current_value.itself).compact
220
229
 
221
- ar_preloader.send(:preloaders_for_reflection, reflection, records, scope).first
230
+ klass = preload_class(reflection)
231
+ args = [reflection.klass, records, reflection, scope]
232
+ args << nil if klass.instance_method(:initialize).arity == 6 # Rails 7
233
+ klass.new(*args, true).run
222
234
  end
223
235
 
224
- # Get the cached instance of active record prelaoder
225
- def ar_preloader
226
- event.request.cache(:ar_preloader) { ::ActiveRecord::Associations::Preloader.new }
236
+ # Get the cached instance of active record preloader
237
+ def preload_class(reflection)
238
+ if reflection.options[:through]
239
+ ::ActiveRecord::Associations::Preloader::ThroughAssociation
240
+ else
241
+ ::ActiveRecord::Associations::Preloader::Association
242
+ end
227
243
  end
228
244
 
229
245
  private
230
246
 
231
- def preloader_association # :nodoc:
247
+ def preloader_association
232
248
  ActiveRecord::Associations::Preloader::Association
233
249
  end
234
250
  end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module GraphQL
5
+ class Source
6
+ class Base < GraphQL::Source
7
+ extend Helpers::WithSchemaFields
8
+ extend Helpers::WithAssignment
9
+ extend Helpers::Unregisterable
10
+
11
+ self.abstract = true
12
+
13
+ # The name of the class (or the class itself) to be used as superclass
14
+ # for the generate GraphQL object type of this source
15
+ class_attribute :object_class, instance_accessor: false,
16
+ default: '::Rails::GraphQL::Type::Object'
17
+
18
+ # The name of the class (or the class itself) to be used as superclass
19
+ # for the generate GraphQL input type of this source
20
+ class_attribute :input_class, instance_accessor: false,
21
+ default: '::Rails::GraphQL::Type::Input'
22
+
23
+ # Allow defining a name for the object without going to many troubles
24
+ # like overriding methods
25
+ class_attribute :object_name, instance_accessor: false
26
+
27
+ # Allow defining a name for the input without going to many troubles
28
+ # like overriding methods
29
+ class_attribute :input_name, instance_accessor: false
30
+
31
+ class << self
32
+
33
+ # Unregister all objects that this source was providing
34
+ def unregister!
35
+ GraphQL.type_map.unregister(*created_types) if defined?(@created_types)
36
+ @object = @input = nil
37
+ end
38
+
39
+ # Return the GraphQL object type associated with the source. It will
40
+ # create one if it's not defined yet. The created class will be added
41
+ # to the +::GraphQL+ namespace with the addition of any namespace of
42
+ # the current class
43
+ def object
44
+ @object ||= create_type(superclass: object_class, gql_name: object_name)
45
+ end
46
+
47
+ # Return the GraphQL input type associated with the source. It will
48
+ # create one if it's not defined yet. The created class will be added
49
+ # to the +::GraphQL+ namespace with the addition of any namespace of
50
+ # the current class
51
+ def input
52
+ @input ||= create_type(superclass: input_class, gql_name: input_name)
53
+ end
54
+
55
+ protected
56
+
57
+ # A helper method to create an enum type
58
+ def create_enum(enum_name, values, **xargs, &block)
59
+ enumerator = values.each_pair if values.respond_to?(:each_pair)
60
+ enumerator ||= values.each.with_index
61
+
62
+ xargs = xargs.reverse_merge(once: true)
63
+ create_type(:enum, as: enum_name.classify, **xargs) do
64
+ indexed! if enumerator.first.last.is_a?(Numeric)
65
+ enumerator.sort_by(&:last).map(&:first).each(&method(:add))
66
+ instance_exec(&block) if block.present?
67
+ end
68
+ end
69
+
70
+ # Helper method to create a class based on the given +type+ and
71
+ # allows several other settings to be executed on it
72
+ def create_type(type = nil, **xargs, &block)
73
+ name = "#{gql_module.name}::#{xargs.delete(:as) || base_name}"
74
+ superclass = xargs.delete(:superclass)
75
+ with_owner = xargs.delete(:with_owner)
76
+
77
+ if superclass.nil?
78
+ superclass = type.to_s.classify
79
+ elsif superclass.is_a?(String)
80
+ superclass = superclass.constantize
81
+ end
82
+
83
+ source = self
84
+ gql_name = xargs.delete(:gql_name)
85
+ Schema.send(:create_type, name, superclass, **xargs) do
86
+ include Helpers::WithOwner if with_owner
87
+ set_namespaces(*source.namespaces)
88
+
89
+ instance_variable_set(:@gql_name, gql_name) unless gql_name.nil?
90
+
91
+ self.owner = source if respond_to?(:owner=)
92
+ self.assigned_to = source.safe_assigned_class \
93
+ if source.assigned? && is_a?(Helpers::WithAssignment)
94
+
95
+ instance_exec(&block) if block.present?
96
+ end.tap { |klass| created_types << klass }
97
+ end
98
+
99
+ private
100
+
101
+ # Keep track of all the types created byt this source
102
+ def created_types
103
+ @created_types ||= []
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module GraphQL
5
+ class Source
6
+ module Builder
7
+
8
+ # Check if the object was already built
9
+ def built?(part = nil)
10
+ defined?(@built) && @built.present? &&
11
+ (part.nil? || @built.include?(part.to_sym))
12
+ end
13
+
14
+ # Build all from all descendant sources
15
+ def build_all_sources
16
+ descendants.each(&:build_all)
17
+ end
18
+
19
+ # Trigger a safe build of everything
20
+ def build_all
21
+ build_all! unless abstract?
22
+ end
23
+
24
+ # Allows building anything that is in hooks
25
+ def respond_to_missing?(method_name, *)
26
+ return super unless method_name.to_s.start_with?('build_') &&
27
+ hook_names.include?(method_name.to_s[6..-1].to_sym)
28
+ end
29
+
30
+ # Allow fast creation of values
31
+ def method_missing(method_name, *args, **xargs, &block)
32
+ return super unless method_name.to_s.start_with?('build_')
33
+
34
+ type = method_name.to_s[6..-1]
35
+ type = type.singularize unless hook_names.include?(type.to_sym)
36
+ type = type.to_sym
37
+
38
+ import_skips_for(type, xargs)
39
+
40
+ build!(type, *args, **xargs, &block) unless built?(type)
41
+ end
42
+
43
+ protected
44
+
45
+ # Store the list of built steps
46
+ def built
47
+ @built ||= Set.new
48
+ end
49
+
50
+ # Make sure to mark the hook name as built
51
+ def run_hooks(hook_name, *)
52
+ super
53
+ ensure
54
+ built << hook_name
55
+ end
56
+
57
+ private
58
+
59
+ # Import all options-based settings for skipping field
60
+ def import_skips_for(type, options)
61
+ return if type == :all
62
+
63
+ if !(values = options.delete(:except)).nil?
64
+ segmented_skip_fields[type] += GraphQL.enumerate(values).map do |value|
65
+ value.to_s.underscore
66
+ end
67
+ end
68
+
69
+ if !(values = options.delete(:only)).nil?
70
+ segmented_only_fields[type] += GraphQL.enumerate(values).map do |value|
71
+ value.to_s.underscore
72
+ end
73
+ end
74
+ end
75
+
76
+ # Build all the hooks if it's not built
77
+ def build_all!
78
+ catch(:done) do
79
+ hook_names.to_enum.drop(1).each do |hook|
80
+ send("build_#{hook}")
81
+ end
82
+ end
83
+ end
84
+
85
+ # Check if the build process can happen
86
+ def ensure_build!(type)
87
+ raise DefinitionError, (+<<~MSG).squish if hook_names.exclude?(type)
88
+ #{self.name} doesn't know how build #{type}, hook not proper defined.
89
+ MSG
90
+
91
+ raise DefinitionError, (+<<~MSG).squish if abstract
92
+ Abstract source #{name} cannot be built.
93
+ MSG
94
+
95
+ raise DefinitionError, (+<<~MSG).squish if built?(type)
96
+ The #{type} part is already built.
97
+ MSG
98
+ end
99
+
100
+ # Build all the objects associated with this source
101
+ def build!(type)
102
+ ensure_build!(type)
103
+
104
+ catch(:skip) { run_hooks(:start) } unless built?(:start)
105
+ catch(:skip) { run_hooks(type, hook_scope_for(type)) }
106
+
107
+ built << type.to_sym
108
+ end
109
+
110
+ # Get the correct +self_object+ for the hook instance
111
+ def hook_scope_for(type)
112
+ type = type.to_sym
113
+ object =
114
+ if Helpers::WithSchemaFields::TYPE_FIELD_CLASS.key?(type)
115
+ Helpers::WithSchemaFields::ScopedConfig.new(self, type)
116
+ else
117
+ Helpers::AttributeDelegator.new(self, type)
118
+ end
119
+
120
+ Source::ScopedConfig.new(self, object, type)
121
+ end
122
+
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+
@@ -1,13 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Rails # :nodoc:
4
- module GraphQL # :nodoc:
3
+ module Rails
4
+ module GraphQL
5
5
  # This is a helper class that allows sources to have scoped-based arguments,
6
6
  # meaning that when an argument is present, it triggers the underlying block
7
7
  # on the fields where the argument was attached to
8
- #
9
- # TODO: Easy the usage of scoped arguments with AR, to map model scopes as
10
- # arguments using the abilities here provided
11
8
  module Source::ScopedArguments
12
9
  def self.included(other)
13
10
  other.extend(ClassMethods)
@@ -15,26 +12,37 @@ module Rails # :nodoc:
15
12
 
16
13
  # Extended argument class to be the instance of the scoped
17
14
  class Argument < GraphQL::Argument
18
- attr_reader :block, :fields
19
-
20
- def initialize(*args, on: nil, **xargs, &block)
15
+ def initialize(*args, block:, on: nil, **xargs)
21
16
  super(*args, **xargs)
22
17
 
23
18
  @block = block
24
- @fields = Array.wrap(on).presence
19
+ @fields = GraphQL.enumerate(on)
20
+ end
21
+
22
+ # Apply the argument block to the given object, using or not the value
23
+ def apply_to(object, value)
24
+ callable = @block.is_a?(Symbol) ? object.method(@block) : @block
25
+ raise ::ArgumentError, (+<<~MSG) unless callable.respond_to?(:call)
26
+ Unable to call "#{@block.inspect}" on #{object.class}.
27
+ MSG
28
+
29
+ args = (callable.arity == 1 || callable.arity == -1) ? [value] : nil
30
+
31
+ return callable.call(*args) if callable.is_a?(Method)
32
+ object.instance_exec(*args, &callable)
25
33
  end
26
34
 
27
35
  # Check if the argument should be attached to the given +field+
28
36
  def attach_to?(field)
29
- return true if fields.nil?
37
+ return true if @fields.nil?
30
38
 
31
- fields.any? do |item|
39
+ @fields.any? do |item|
32
40
  (item.is_a?(Symbol) && field.name.eql?(item)) || field.gql_name.eql?(item)
33
41
  end
34
42
  end
35
43
  end
36
44
 
37
- module ClassMethods # :nodoc:
45
+ module ClassMethods
38
46
  # Return the list of scoped params defined
39
47
  def scoped_arguments
40
48
  defined?(@scoped_arguments) ? @scoped_arguments : {}
@@ -45,11 +53,11 @@ module Rails # :nodoc:
45
53
  # Add a new scoped param to the list
46
54
  def scoped_argument(param, type = :string, proc_method = nil, **settings, &block)
47
55
  block = proc_method if proc_method.present? && block.nil?
48
- argument = Argument.new(param, type, **settings, owner: self, &block)
56
+ argument = Argument.new(param, type, **settings, owner: self, block: block)
49
57
  (@scoped_arguments ||= {})[argument.name] = argument
50
58
  end
51
59
 
52
- alias scoped_arg scoped_arguments
60
+ alias scoped_arg scoped_argument
53
61
 
54
62
  # Helper method to attach the scoped arguments to a given +field+
55
63
  def attach_scoped_arguments_to(*fields, safe: true)
@@ -69,14 +77,18 @@ module Rails # :nodoc:
69
77
  # Find all the executable arguments attached to the running field and
70
78
  # call them with the given object
71
79
  def inject_scopes(object, assigned_to = nil)
72
- return object if event.field.nil? || (args_source = event.send(:args_source)).nil?
80
+ return object if event.field.nil? || (field_args = event.field.all_arguments).blank?
73
81
 
82
+ args_source = event.send(:args_source)
74
83
  event.data[assigned_to] ||= object unless assigned_to.nil?
75
- event.field.all_arguments.each_value.inject(object) do |result, argument|
76
- next result unless argument.respond_to?(:block) && args_source.key?(argument.name)
77
- send_args = argument.block.arity.eql?(1) ? [args_source[argument.name]] : []
84
+ field_args.each_value.inject(object) do |result, argument|
85
+ arg_value = args_source.key?(argument.name) \
86
+ ? args_source[argument.name] \
87
+ : argument.default
88
+
89
+ next result if arg_value.nil? || !argument.is_a?(Argument)
78
90
 
79
- value = result.instance_exec(*send_args, &argument.block)
91
+ value = argument.apply_to(result, arg_value)
80
92
  value = value.nil? ? result : value
81
93
 
82
94
  assigned_to.nil? ? value : event.data[assigned_to] = value