rails-graphql 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (266) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +19 -0
  4. data/Rakefile +31 -0
  5. data/ext/depend +3 -0
  6. data/ext/extconf.rb +57 -0
  7. data/ext/graphqlparser/Ast.cpp +346 -0
  8. data/ext/graphqlparser/Ast.h +1214 -0
  9. data/ext/graphqlparser/AstNode.h +36 -0
  10. data/ext/graphqlparser/AstVisitor.h +137 -0
  11. data/ext/graphqlparser/GraphQLParser.cpp +76 -0
  12. data/ext/graphqlparser/GraphQLParser.h +55 -0
  13. data/ext/graphqlparser/JsonVisitor.cpp +161 -0
  14. data/ext/graphqlparser/JsonVisitor.cpp.inc +456 -0
  15. data/ext/graphqlparser/JsonVisitor.h +121 -0
  16. data/ext/graphqlparser/JsonVisitor.h.inc +110 -0
  17. data/ext/graphqlparser/VERSION +1 -0
  18. data/ext/graphqlparser/c/GraphQLAst.cpp +324 -0
  19. data/ext/graphqlparser/c/GraphQLAst.h +180 -0
  20. data/ext/graphqlparser/c/GraphQLAstForEachConcreteType.h +44 -0
  21. data/ext/graphqlparser/c/GraphQLAstNode.cpp +25 -0
  22. data/ext/graphqlparser/c/GraphQLAstNode.h +33 -0
  23. data/ext/graphqlparser/c/GraphQLAstToJSON.cpp +21 -0
  24. data/ext/graphqlparser/c/GraphQLAstToJSON.h +24 -0
  25. data/ext/graphqlparser/c/GraphQLAstVisitor.cpp +55 -0
  26. data/ext/graphqlparser/c/GraphQLAstVisitor.h +53 -0
  27. data/ext/graphqlparser/c/GraphQLParser.cpp +35 -0
  28. data/ext/graphqlparser/c/GraphQLParser.h +54 -0
  29. data/ext/graphqlparser/dump_json_ast.cpp +48 -0
  30. data/ext/graphqlparser/lexer.lpp +324 -0
  31. data/ext/graphqlparser/parser.ypp +693 -0
  32. data/ext/graphqlparser/parsergen/lexer.cpp +2633 -0
  33. data/ext/graphqlparser/parsergen/lexer.h +528 -0
  34. data/ext/graphqlparser/parsergen/location.hh +189 -0
  35. data/ext/graphqlparser/parsergen/parser.tab.cpp +3300 -0
  36. data/ext/graphqlparser/parsergen/parser.tab.hpp +646 -0
  37. data/ext/graphqlparser/parsergen/position.hh +179 -0
  38. data/ext/graphqlparser/parsergen/stack.hh +156 -0
  39. data/ext/graphqlparser/syntaxdefs.h +19 -0
  40. data/ext/libgraphqlparser/AstNode.h +36 -0
  41. data/ext/libgraphqlparser/CMakeLists.txt +148 -0
  42. data/ext/libgraphqlparser/CONTRIBUTING.md +23 -0
  43. data/ext/libgraphqlparser/GraphQLParser.cpp +76 -0
  44. data/ext/libgraphqlparser/GraphQLParser.h +55 -0
  45. data/ext/libgraphqlparser/JsonVisitor.cpp +161 -0
  46. data/ext/libgraphqlparser/JsonVisitor.h +121 -0
  47. data/ext/libgraphqlparser/LICENSE +22 -0
  48. data/ext/libgraphqlparser/README.clang-tidy +7 -0
  49. data/ext/libgraphqlparser/README.md +84 -0
  50. data/ext/libgraphqlparser/ast/ast.ast +203 -0
  51. data/ext/libgraphqlparser/ast/ast.py +61 -0
  52. data/ext/libgraphqlparser/ast/c.py +100 -0
  53. data/ext/libgraphqlparser/ast/c.pyc +0 -0
  54. data/ext/libgraphqlparser/ast/c_impl.py +61 -0
  55. data/ext/libgraphqlparser/ast/c_impl.pyc +0 -0
  56. data/ext/libgraphqlparser/ast/c_visitor_impl.py +39 -0
  57. data/ext/libgraphqlparser/ast/c_visitor_impl.pyc +0 -0
  58. data/ext/libgraphqlparser/ast/casing.py +26 -0
  59. data/ext/libgraphqlparser/ast/casing.pyc +0 -0
  60. data/ext/libgraphqlparser/ast/cxx.py +197 -0
  61. data/ext/libgraphqlparser/ast/cxx.pyc +0 -0
  62. data/ext/libgraphqlparser/ast/cxx_impl.py +61 -0
  63. data/ext/libgraphqlparser/ast/cxx_impl.pyc +0 -0
  64. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.py +42 -0
  65. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.pyc +0 -0
  66. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.py +80 -0
  67. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.pyc +0 -0
  68. data/ext/libgraphqlparser/ast/cxx_visitor.py +64 -0
  69. data/ext/libgraphqlparser/ast/cxx_visitor.pyc +0 -0
  70. data/ext/libgraphqlparser/ast/js.py +65 -0
  71. data/ext/libgraphqlparser/ast/license.py +10 -0
  72. data/ext/libgraphqlparser/ast/license.pyc +0 -0
  73. data/ext/libgraphqlparser/c/GraphQLAstNode.cpp +25 -0
  74. data/ext/libgraphqlparser/c/GraphQLAstNode.h +33 -0
  75. data/ext/libgraphqlparser/c/GraphQLAstToJSON.cpp +21 -0
  76. data/ext/libgraphqlparser/c/GraphQLAstToJSON.h +24 -0
  77. data/ext/libgraphqlparser/c/GraphQLAstVisitor.cpp +55 -0
  78. data/ext/libgraphqlparser/c/GraphQLAstVisitor.h +53 -0
  79. data/ext/libgraphqlparser/c/GraphQLParser.cpp +35 -0
  80. data/ext/libgraphqlparser/c/GraphQLParser.h +54 -0
  81. data/ext/libgraphqlparser/clang-tidy-all.sh +3 -0
  82. data/ext/libgraphqlparser/cmake/version.cmake +16 -0
  83. data/ext/libgraphqlparser/dump_json_ast.cpp +48 -0
  84. data/ext/libgraphqlparser/go/README.md +20 -0
  85. data/ext/libgraphqlparser/go/callbacks.go +18 -0
  86. data/ext/libgraphqlparser/go/gotest.go +64 -0
  87. data/ext/libgraphqlparser/lexer.lpp +324 -0
  88. data/ext/libgraphqlparser/libgraphqlparser.pc.in +11 -0
  89. data/ext/libgraphqlparser/parser.ypp +693 -0
  90. data/ext/libgraphqlparser/parsergen/lexer.cpp +2633 -0
  91. data/ext/libgraphqlparser/parsergen/lexer.h +528 -0
  92. data/ext/libgraphqlparser/parsergen/location.hh +189 -0
  93. data/ext/libgraphqlparser/parsergen/parser.tab.cpp +3300 -0
  94. data/ext/libgraphqlparser/parsergen/parser.tab.hpp +646 -0
  95. data/ext/libgraphqlparser/parsergen/position.hh +179 -0
  96. data/ext/libgraphqlparser/parsergen/stack.hh +156 -0
  97. data/ext/libgraphqlparser/python/CMakeLists.txt +14 -0
  98. data/ext/libgraphqlparser/python/README.md +5 -0
  99. data/ext/libgraphqlparser/python/example.py +31 -0
  100. data/ext/libgraphqlparser/syntaxdefs.h +19 -0
  101. data/ext/libgraphqlparser/test/BuildCAPI.c +5 -0
  102. data/ext/libgraphqlparser/test/CMakeLists.txt +25 -0
  103. data/ext/libgraphqlparser/test/JsonVisitorTests.cpp +28 -0
  104. data/ext/libgraphqlparser/test/ParserTests.cpp +352 -0
  105. data/ext/libgraphqlparser/test/kitchen-sink.graphql +59 -0
  106. data/ext/libgraphqlparser/test/kitchen-sink.json +1 -0
  107. data/ext/libgraphqlparser/test/schema-kitchen-sink.graphql +78 -0
  108. data/ext/libgraphqlparser/test/schema-kitchen-sink.json +1 -0
  109. data/ext/libgraphqlparser/test/valgrind.supp +33 -0
  110. data/ext/version.cpp +21 -0
  111. data/lib/generators/graphql/controller_generator.rb +22 -0
  112. data/lib/generators/graphql/schema_generator.rb +22 -0
  113. data/lib/generators/graphql/templates/controller.erb +5 -0
  114. data/lib/generators/graphql/templates/schema.erb +6 -0
  115. data/lib/graphqlparser.so +0 -0
  116. data/lib/rails-graphql.rb +2 -0
  117. data/lib/rails/graphql.rake +1 -0
  118. data/lib/rails/graphql.rb +185 -0
  119. data/lib/rails/graphql/adapters/mysql_adapter.rb +0 -0
  120. data/lib/rails/graphql/adapters/pg_adapter.rb +50 -0
  121. data/lib/rails/graphql/adapters/sqlite_adapter.rb +39 -0
  122. data/lib/rails/graphql/argument.rb +220 -0
  123. data/lib/rails/graphql/callback.rb +124 -0
  124. data/lib/rails/graphql/collectors.rb +14 -0
  125. data/lib/rails/graphql/collectors/hash_collector.rb +83 -0
  126. data/lib/rails/graphql/collectors/idented_collector.rb +73 -0
  127. data/lib/rails/graphql/collectors/json_collector.rb +114 -0
  128. data/lib/rails/graphql/config.rb +61 -0
  129. data/lib/rails/graphql/directive.rb +203 -0
  130. data/lib/rails/graphql/directive/deprecated_directive.rb +59 -0
  131. data/lib/rails/graphql/directive/include_directive.rb +24 -0
  132. data/lib/rails/graphql/directive/skip_directive.rb +24 -0
  133. data/lib/rails/graphql/errors.rb +42 -0
  134. data/lib/rails/graphql/event.rb +141 -0
  135. data/lib/rails/graphql/field.rb +318 -0
  136. data/lib/rails/graphql/field/input_field.rb +92 -0
  137. data/lib/rails/graphql/field/mutation_field.rb +52 -0
  138. data/lib/rails/graphql/field/output_field.rb +96 -0
  139. data/lib/rails/graphql/field/proxied_field.rb +131 -0
  140. data/lib/rails/graphql/field/resolved_field.rb +96 -0
  141. data/lib/rails/graphql/field/scoped_config.rb +22 -0
  142. data/lib/rails/graphql/field/typed_field.rb +104 -0
  143. data/lib/rails/graphql/helpers.rb +40 -0
  144. data/lib/rails/graphql/helpers/attribute_delegator.rb +39 -0
  145. data/lib/rails/graphql/helpers/inherited_collection.rb +152 -0
  146. data/lib/rails/graphql/helpers/leaf_from_ar.rb +141 -0
  147. data/lib/rails/graphql/helpers/registerable.rb +103 -0
  148. data/lib/rails/graphql/helpers/with_arguments.rb +125 -0
  149. data/lib/rails/graphql/helpers/with_assignment.rb +113 -0
  150. data/lib/rails/graphql/helpers/with_callbacks.rb +55 -0
  151. data/lib/rails/graphql/helpers/with_directives.rb +126 -0
  152. data/lib/rails/graphql/helpers/with_events.rb +81 -0
  153. data/lib/rails/graphql/helpers/with_fields.rb +141 -0
  154. data/lib/rails/graphql/helpers/with_namespace.rb +40 -0
  155. data/lib/rails/graphql/helpers/with_owner.rb +35 -0
  156. data/lib/rails/graphql/helpers/with_schema_fields.rb +230 -0
  157. data/lib/rails/graphql/helpers/with_validator.rb +52 -0
  158. data/lib/rails/graphql/introspection.rb +53 -0
  159. data/lib/rails/graphql/native.rb +56 -0
  160. data/lib/rails/graphql/native/functions.rb +38 -0
  161. data/lib/rails/graphql/native/location.rb +41 -0
  162. data/lib/rails/graphql/native/pointers.rb +23 -0
  163. data/lib/rails/graphql/native/visitor.rb +349 -0
  164. data/lib/rails/graphql/railtie.rb +85 -0
  165. data/lib/rails/graphql/railties/base_generator.rb +35 -0
  166. data/lib/rails/graphql/railties/controller.rb +101 -0
  167. data/lib/rails/graphql/railties/controller_runtime.rb +40 -0
  168. data/lib/rails/graphql/railties/log_subscriber.rb +62 -0
  169. data/lib/rails/graphql/request.rb +343 -0
  170. data/lib/rails/graphql/request/arguments.rb +93 -0
  171. data/lib/rails/graphql/request/component.rb +100 -0
  172. data/lib/rails/graphql/request/component/field.rb +225 -0
  173. data/lib/rails/graphql/request/component/fragment.rb +118 -0
  174. data/lib/rails/graphql/request/component/operation.rb +178 -0
  175. data/lib/rails/graphql/request/component/operation/subscription.rb +16 -0
  176. data/lib/rails/graphql/request/component/spread.rb +119 -0
  177. data/lib/rails/graphql/request/component/typename.rb +82 -0
  178. data/lib/rails/graphql/request/context.rb +51 -0
  179. data/lib/rails/graphql/request/errors.rb +54 -0
  180. data/lib/rails/graphql/request/event.rb +112 -0
  181. data/lib/rails/graphql/request/helpers/directives.rb +64 -0
  182. data/lib/rails/graphql/request/helpers/selection_set.rb +87 -0
  183. data/lib/rails/graphql/request/helpers/value_writers.rb +115 -0
  184. data/lib/rails/graphql/request/steps/organizable.rb +146 -0
  185. data/lib/rails/graphql/request/steps/prepareable.rb +33 -0
  186. data/lib/rails/graphql/request/steps/resolveable.rb +32 -0
  187. data/lib/rails/graphql/request/strategy.rb +249 -0
  188. data/lib/rails/graphql/request/strategy/dynamic_instance.rb +41 -0
  189. data/lib/rails/graphql/request/strategy/multi_query_strategy.rb +36 -0
  190. data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +28 -0
  191. data/lib/rails/graphql/schema.rb +272 -0
  192. data/lib/rails/graphql/shortcuts.rb +77 -0
  193. data/lib/rails/graphql/source.rb +371 -0
  194. data/lib/rails/graphql/source/active_record/builders.rb +154 -0
  195. data/lib/rails/graphql/source/active_record_source.rb +231 -0
  196. data/lib/rails/graphql/source/scoped_arguments.rb +87 -0
  197. data/lib/rails/graphql/to_gql.rb +368 -0
  198. data/lib/rails/graphql/type.rb +138 -0
  199. data/lib/rails/graphql/type/enum.rb +206 -0
  200. data/lib/rails/graphql/type/enum/directive_location_enum.rb +30 -0
  201. data/lib/rails/graphql/type/enum/type_kind_enum.rb +57 -0
  202. data/lib/rails/graphql/type/input.rb +134 -0
  203. data/lib/rails/graphql/type/interface.rb +82 -0
  204. data/lib/rails/graphql/type/object.rb +111 -0
  205. data/lib/rails/graphql/type/object/directive_object.rb +34 -0
  206. data/lib/rails/graphql/type/object/enum_value_object.rb +25 -0
  207. data/lib/rails/graphql/type/object/field_object.rb +54 -0
  208. data/lib/rails/graphql/type/object/input_value_object.rb +49 -0
  209. data/lib/rails/graphql/type/object/schema_object.rb +40 -0
  210. data/lib/rails/graphql/type/object/type_object.rb +136 -0
  211. data/lib/rails/graphql/type/scalar.rb +71 -0
  212. data/lib/rails/graphql/type/scalar/bigint_scalar.rb +34 -0
  213. data/lib/rails/graphql/type/scalar/binary_scalar.rb +30 -0
  214. data/lib/rails/graphql/type/scalar/boolean_scalar.rb +37 -0
  215. data/lib/rails/graphql/type/scalar/date_scalar.rb +34 -0
  216. data/lib/rails/graphql/type/scalar/date_time_scalar.rb +32 -0
  217. data/lib/rails/graphql/type/scalar/decimal_scalar.rb +35 -0
  218. data/lib/rails/graphql/type/scalar/float_scalar.rb +32 -0
  219. data/lib/rails/graphql/type/scalar/id_scalar.rb +39 -0
  220. data/lib/rails/graphql/type/scalar/int_scalar.rb +36 -0
  221. data/lib/rails/graphql/type/scalar/string_scalar.rb +28 -0
  222. data/lib/rails/graphql/type/scalar/time_scalar.rb +40 -0
  223. data/lib/rails/graphql/type/union.rb +87 -0
  224. data/lib/rails/graphql/type_map.rb +347 -0
  225. data/lib/rails/graphql/version.rb +7 -0
  226. data/test/assets/introspection-db.json +0 -0
  227. data/test/assets/introspection-mem.txt +1 -0
  228. data/test/assets/introspection.gql +91 -0
  229. data/test/assets/luke.jpg +0 -0
  230. data/test/assets/mem.gql +428 -0
  231. data/test/assets/sqlite.gql +423 -0
  232. data/test/config.rb +80 -0
  233. data/test/graphql/request/context_test.rb +70 -0
  234. data/test/graphql/schema_test.rb +190 -0
  235. data/test/graphql/source_test.rb +237 -0
  236. data/test/graphql/type/enum_test.rb +203 -0
  237. data/test/graphql/type/input_test.rb +138 -0
  238. data/test/graphql/type/interface_test.rb +72 -0
  239. data/test/graphql/type/object_test.rb +104 -0
  240. data/test/graphql/type/scalar/bigint_scalar_test.rb +42 -0
  241. data/test/graphql/type/scalar/binary_scalar_test.rb +17 -0
  242. data/test/graphql/type/scalar/boolean_scalar_test.rb +40 -0
  243. data/test/graphql/type/scalar/date_scalar_test.rb +29 -0
  244. data/test/graphql/type/scalar/date_time_scalar_test.rb +29 -0
  245. data/test/graphql/type/scalar/decimal_scalar_test.rb +28 -0
  246. data/test/graphql/type/scalar/float_scalar_test.rb +22 -0
  247. data/test/graphql/type/scalar/id_scalar_test.rb +26 -0
  248. data/test/graphql/type/scalar/int_scalar_test.rb +26 -0
  249. data/test/graphql/type/scalar/string_scalar_test.rb +17 -0
  250. data/test/graphql/type/scalar/time_scalar_test.rb +36 -0
  251. data/test/graphql/type/scalar_test.rb +45 -0
  252. data/test/graphql/type/union_test.rb +82 -0
  253. data/test/graphql/type_map_test.rb +362 -0
  254. data/test/graphql/type_test.rb +68 -0
  255. data/test/graphql_test.rb +55 -0
  256. data/test/integration/config.rb +56 -0
  257. data/test/integration/memory/star_wars_introspection_test.rb +144 -0
  258. data/test/integration/memory/star_wars_query_test.rb +184 -0
  259. data/test/integration/memory/star_wars_validation_test.rb +99 -0
  260. data/test/integration/schemas/memory.rb +232 -0
  261. data/test/integration/schemas/sqlite.rb +82 -0
  262. data/test/integration/sqlite/star_wars_introspection_test.rb +15 -0
  263. data/test/integration/sqlite/star_wars_mutation_test.rb +82 -0
  264. data/test/integration/sqlite/star_wars_query_test.rb +71 -0
  265. data/test/test_ext.rb +48 -0
  266. metadata +509 -0
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This exposed module allows some shortcuts while working outside of the gem
4
+ module GraphQL
5
+ # List of constant shortcuts, as string to not trigger autoload
6
+ CONST_SHORTCUTS = {
7
+ Controller: '::Rails::GraphQL::Controller',
8
+ Directive: '::Rails::GraphQL::Directive',
9
+ Field: '::Rails::GraphQL::Field',
10
+ Mutation: '::Rails::GraphQL::Mutation',
11
+ Request: '::Rails::GraphQL::Request',
12
+ Schema: '::Rails::GraphQL::Schema',
13
+ Source: '::Rails::GraphQL::Source',
14
+
15
+ Enum: '::Rails::GraphQL::Type::Enum',
16
+ Input: '::Rails::GraphQL::Type::Input',
17
+ Interface: '::Rails::GraphQL::Type::Interface',
18
+ Object: '::Rails::GraphQL::Type::Object',
19
+ Scalar: '::Rails::GraphQL::Type::Scalar',
20
+ Union: '::Rails::GraphQL::Type::Union',
21
+
22
+ ProxyField: '::Rails::GraphQL::Field::ProxyField',
23
+ AssociationField: '::Rails::GraphQL::Field::AssociationField',
24
+
25
+ ActiveRecordSource: '::Rails::GraphQL::Source::ActiveRecordSource',
26
+ }.freeze
27
+
28
+ # List of directive shortcuts, which are basically the shortcut of another
29
+ # shortcut to instantiate a directive.
30
+ #
31
+ # ==== Examples
32
+ #
33
+ # GraphQL::DeprecatedDirective(...)
34
+ # # => Rails::GraphQL::Directive::DeprecatedDirective(...)
35
+ #
36
+ # Rails::GraphQL::Directive::DeprecatedDirective(...)
37
+ # # => Rails::GraphQL::Directive::DeprecatedDirective.new(...)
38
+ DIRECTIVE_SHORTCUTS = %i[DeprecatedDirective IncludeDirective SkipDirective].freeze
39
+
40
+ class << self
41
+ delegate(:to_gql, :to_graphql, :type_map, to: 'Rails::GraphQL')
42
+ delegate(*DIRECTIVE_SHORTCUTS, to: 'Rails::GraphQL::Directive')
43
+
44
+ # See {Request}[rdoc-ref:Rails::GraphQL::Request]
45
+ def request(*args, **xargs)
46
+ Rails::GraphQL::Request.new(*args, **xargs)
47
+ end
48
+
49
+ # See {Request}[rdoc-ref:Rails::GraphQL::Request]
50
+ def execute(*args, **xargs)
51
+ Rails::GraphQL::Request.execute(*args, **xargs)
52
+ end
53
+
54
+ alias perform execute
55
+
56
+ # See {CONST_SHORTCUTS}[rdoc-ref:GraphQL::CONST_SHORTCUTS]
57
+ def const_defined?(name, *)
58
+ name = :"ActiveRecord#{name[2..-1]}" if name[0..1] === 'AR'
59
+ CONST_SHORTCUTS.key?(name) || super
60
+ end
61
+
62
+ # See {CONST_SHORTCUTS}[rdoc-ref:GraphQL::CONST_SHORTCUTS]
63
+ def const_missing(name)
64
+ name = :"ActiveRecord#{name[2..-1]}" if name[0..1] === 'AR'
65
+ return resolved[name] if resolved.key?(name)
66
+ return super unless CONST_SHORTCUTS.key?(name)
67
+ resolved[name] = CONST_SHORTCUTS[name].constantize
68
+ end
69
+
70
+ private
71
+
72
+ # Stores resolved constants for increased performance
73
+ def resolved
74
+ @@resolved = {}
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,371 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ # = GraphQL Source
6
+ #
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
10
+ class Source
11
+ extend ActiveSupport::Autoload
12
+
13
+ extend Helpers::InheritedCollection
14
+ extend Helpers::WithSchemaFields
15
+ extend Helpers::WithAssignment
16
+ extend Helpers::WithNamespace
17
+
18
+ DEFAULT_NAMESPACES = %i[base].freeze
19
+
20
+ eager_autoload do
21
+ autoload :ScopedArguments
22
+
23
+ autoload :ActiveRecordSource
24
+ end
25
+
26
+ ScopedConfig = Struct.new(:receiver, :self_object) do # :nodoc: all
27
+ def respond_to_missing?(method_name, include_private = false)
28
+ self_object.respond_to?(method_name, include_private) ||
29
+ receiver.respond_to?(method_name, include_private)
30
+ end
31
+
32
+ def method_missing(method_name, *args, **xargs, &block)
33
+ self_object.respond_to?(method_name, true) \
34
+ ? self_object.send(method_name, *args, **xargs, &block) \
35
+ : receiver.send(method_name, *args, **xargs, &block)
36
+ end
37
+ end
38
+
39
+ # If a source is marked as abstract, it means that it generates a new
40
+ # source describer and any non-abstract class inherited from it will be
41
+ # described by this new abstraction
42
+ class_attribute :abstract, instance_writer: false, default: false
43
+
44
+ # List of hook names used while describing a new source. This basically
45
+ # 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
50
+
51
+ # The list of hooks defined in order to describe a source
52
+ inherited_collection :hooks, instance_reader: false, type: :hash_array
53
+
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
+ # A list of fields to skip when performing shared methods
69
+ inherited_collection :skip_fields, instance_reader: false
70
+
71
+ # A list of fields to skip but segmented by holder source
72
+ inherited_collection :segmented_skip_fields, instance_reader: false, type: :hash_set
73
+
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
79
+
80
+ self.abstract = true
81
+
82
+ class << self
83
+ attr_reader :schemas
84
+
85
+ delegate :field, :proxy_field, :overwrite_field, :[], :field?,
86
+ :field_names, :gql_name, to: :object
87
+
88
+ def kind # :nodoc:
89
+ :source
90
+ end
91
+
92
+ # Sources are close related to objects, meaning that they are type based
93
+ def base_type_class
94
+ :Type
95
+ end
96
+
97
+ # Get the main name of the source
98
+ 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
110
+ end
111
+
112
+ # Find a source for a given object. If none is found, then raise an
113
+ # exception
114
+ def find_for!(object)
115
+ find_for(object) || raise(::ArgumentError, <<~MSG.squish)
116
+ Unable to find a source for "#{object.name}".
117
+ MSG
118
+ end
119
+
120
+ # Using the list of +base_sources+, find the first one that can handle
121
+ # the given +object+
122
+ def find_for(object)
123
+ object = object.constantize if object.is_a?(String)
124
+ base_sources.reverse_each.find { |source| object <= source.assigned_class }
125
+ end
126
+
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
+ # Attach all defined schema fields into the schemas using the namespaces
149
+ # configured for the source
150
+ def attach_fields!
151
+ refresh_schemas!
152
+ schemas.each_value do |schema|
153
+ Helpers::WithSchemaFields::SCHEMA_FIELD_TYPES.keys.each do |type|
154
+ list = public_send("#{type}_fields")
155
+ next if list.empty?
156
+
157
+ list.each_value do |field|
158
+ next if schema.has_field?(type, field)
159
+ schema.add_proxy_field(type, field)
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ # Find all the schemas associated with the configured namespaces
166
+ def refresh_schemas!
167
+ @schemas = (namespaces.presence || DEFAULT_NAMESPACES).map do |ns|
168
+ (schema = Schema.find(ns)).present? ? [ns, schema] : nil
169
+ end.compact.to_h
170
+ end
171
+
172
+ def eager_load! # :nodoc:
173
+ super
174
+
175
+ build_pending!
176
+ end
177
+
178
+ protected
179
+
180
+ # Find a given +type+ on the same namespaces of the source. It will
181
+ # raise an exception if the +type+ can not be found
182
+ def find_type!(type, **xargs)
183
+ xargs[:base_class] = :Type
184
+ xargs[:namespaces] = namespaces
185
+ GraphQL.type_map.fetch!(type, **xargs)
186
+ end
187
+
188
+ # A little bypass to the actual type map after register method which
189
+ # just add the namesapace by default
190
+ # See {TypeMap#after_register}[rdoc-ref:Rails::GraphQL::TypeMap#after_register]
191
+ def type_map_after_register(*args, **xargs, &block)
192
+ xargs[:namespaces] ||= namespaces
193
+ GraphQL.type_map.after_register(*args, **xargs, &block)
194
+ end
195
+
196
+ # A helper method to create an enum type
197
+ def create_enum(enum_name, values, **xargs, &block)
198
+ enumerator = values.each_pair if values.respond_to?(:each_pair)
199
+ enumerator ||= values.each.with_index
200
+
201
+ xargs = xargs.reverse_merge(once: true)
202
+ create_type(:enum, as: enum_name.classify, **xargs) do
203
+ indexed! if enumerator.first.last.is_a?(Numeric)
204
+ enumerator.sort_by(&:last).map(&:first).each(&method(:add))
205
+ instance_exec(&block) if block.present?
206
+ end
207
+ end
208
+
209
+ # Helper method to create a class based on the given type and allows
210
+ # several other settings to be executed on it
211
+ def create_type(type = nil, **xargs, &block)
212
+ name = "#{gql_module.name}::#{xargs.delete(:as) || base_name}"
213
+ superclass = xargs.delete(:superclass)
214
+ with_owner = xargs.delete(:with_owner)
215
+
216
+ if superclass.nil?
217
+ superclass = type.to_s.classify
218
+ elsif superclass.is_a?(String)
219
+ superclass = superclass.constantize
220
+ end
221
+
222
+ source = self
223
+ Schema.send(:create_type, name, superclass, **xargs) do
224
+ include Helpers::WithOwner if with_owner
225
+ set_namespaces(*source.namespaces)
226
+
227
+ self.owner = source if respond_to?(:owner=)
228
+ self.assigned_to = source.safe_assigned_class \
229
+ if source.assigned? && is_a?(Helpers::WithAssignment)
230
+
231
+ instance_exec(&block) if block.present?
232
+ end
233
+ end
234
+
235
+ # Add fields to be skipped on the given +source+ as the segment
236
+ def skip_on(source, *fields)
237
+ segmented_skip_fields[source] += fields.flatten.compact.map(&:to_sym).to_set
238
+ end
239
+
240
+ # Add a new description hook. You can use +throw :skip+ and skip
241
+ # parent hooks. If the class is already built, then execute the hook.
242
+ # Use the +unshift: true+ to add the hook at the beginning of the
243
+ # list, which will then be the last to run
244
+ def on(hook_name, unshift: false, &block)
245
+ raise ArgumentError, <<~MSG.squish unless hook_names.include?(hook_name.to_sym)
246
+ The #{hook_name.inspect} is not a valid hook method.
247
+ MSG
248
+
249
+ if built?
250
+ send("run_#{hook_name}_hooks", block)
251
+ else
252
+ hooks[hook_name.to_sym].public_send(unshift ? :unshift : :push, block)
253
+ end
254
+ end
255
+
256
+ # Creates a hook that throws a done action, preventing any parent hooks
257
+ def skip(*names)
258
+ names.each do |hook_name|
259
+ hook_name = hook_name.to_s.singularize.to_sym
260
+ on(hook_name) { throw :skip }
261
+ end
262
+ end
263
+
264
+ # This is a shortcut to +skip hook_name+ and then
265
+ # +on hook_name do; end+
266
+ def override(hook_name, &block)
267
+ skip(hook_name)
268
+ on(hook_name, &block)
269
+ end
270
+
271
+ # It's an alternative to +self.hook_names -= %i[*names]+ which
272
+ # disables a specific hook
273
+ def disable(*names)
274
+ self.hook_names -= names.flatten.map do |hook_name|
275
+ hook_name.to_s.singularize.to_sym
276
+ end
277
+ end
278
+
279
+ # It's an alternative to +self.hook_names += %i[*names]+ which
280
+ # enables additional hooks
281
+ def enable(*names)
282
+ self.hook_names += names.flatten.map do |hook_name|
283
+ hook_name.to_s.singularize.to_sym
284
+ end
285
+ end
286
+
287
+ # Return the module where the GraphQL types should be created at
288
+ def gql_module
289
+ name.starts_with?('GraphQL::') ? module_parent : ::GraphQL
290
+ end
291
+
292
+ # Get the list of fields to be skipped from the given +holder+ as the
293
+ # segment source
294
+ def skips_for(holder)
295
+ segment = holder.kind
296
+ segment = :input if segment.eql?(:input_object)
297
+ segmented = all_segmented_skip_fields[segment]
298
+ segmented.present? ? all_skip_fields + segmented : all_skip_fields
299
+ end
300
+
301
+ private
302
+
303
+ # The list of pending sources to be built asscoaited to where they
304
+ # were defined
305
+ def pending
306
+ @@pending ||= {}
307
+ end
308
+
309
+ # Check if there are pending sources to be built
310
+ def pending?
311
+ pending.any?
312
+ end
313
+
314
+ # Build the pending sources
315
+ def build_pending!
316
+ while (klass, = pending.shift)
317
+ klass.send(:build!) unless klass.abstract?
318
+ end
319
+ end
320
+
321
+ # Find all classes that inherits from source that are abstract,
322
+ # meaning that they are a base sources
323
+ def base_sources
324
+ @@base_sources ||= begin
325
+ eager_load!
326
+ descendants.select(&:abstract?).to_set
327
+ end
328
+ end
329
+
330
+ # Build all the objects associated with this source
331
+ def build!
332
+ return if built?
333
+
334
+ raise DefinitionError, <<~MSG.squish if abstract
335
+ Abstract source #{name} cannot be built.
336
+ MSG
337
+
338
+ @built = true
339
+
340
+ catch(:done) do
341
+ hook_names.each do |hook_name|
342
+ break if hook_name === :finish
343
+ catch(:skip) { send("run_#{hook_name}_hooks") }
344
+ end
345
+ end
346
+
347
+ catch(:skip) { send(:run_finish_hooks) } if respond_to?(:run_finish_hooks, true)
348
+ end
349
+
350
+ {
351
+ start: 'self',
352
+ finish: 'self',
353
+ object: 'Helpers::AttributeDelegator.new(self, :object)',
354
+ input: 'Helpers::AttributeDelegator.new(self, :input)',
355
+ query: format('schema_scoped_config(self, %s)', ':query'),
356
+ mutation: format('schema_scoped_config(self, %s)', ':mutation'),
357
+ subscription: format('schema_scoped_config(self, %s)', ':subscription'),
358
+ }.each do |key, object|
359
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
360
+ def run_#{key}_hooks(list = nil)
361
+ source_config = Source::ScopedConfig.new(self, #{object})
362
+ Array.wrap(list.presence || all_hooks[:#{key}]).reverse_each do |block|
363
+ source_config.instance_exec(&block)
364
+ end
365
+ end
366
+ RUBY
367
+ end
368
+ end
369
+ end
370
+ end
371
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module GraphQL
5
+ # All the helper methods for building the source
6
+ module Source::ActiveRecordSource::Builders
7
+ # Override the object class to identify interfaces due to STI
8
+ def object_class
9
+ sti_interface? ? interface_class : super
10
+ end
11
+
12
+ # Get all unique attribute names that exists in the current model
13
+ def reflection_attributes(holder)
14
+ items = []
15
+ each_reflection(holder) do |item|
16
+ next unless item.belongs_to?
17
+ next items << item.foreign_key.to_s unless item.polymorphic?
18
+ items += [item.foreign_type, item.foreign_key]
19
+ end
20
+
21
+ items.compact.flatten.unshift(primary_key)
22
+ end
23
+
24
+ # Iterate over all the attributes, except the primary key, from the model
25
+ # but already set to be imported to GraphQL fields
26
+ # TODO: Turn into an enumerator
27
+ def each_attribute(holder, skip_primary_key = true)
28
+ adapter_key = GraphQL.ar_adapter_key(adapter_name)
29
+
30
+ skip_fields = skips_for(holder).map(&:to_s)
31
+ skip_fields << model.inheritance_column
32
+ skip_fields << primary_key unless skip_primary_key
33
+
34
+ send("#{adapter_key}_attributes") do |attribute, *args|
35
+ yield attribute, *args unless skip_fields.include?(attribute)
36
+ end
37
+ end
38
+
39
+ # Iterate over all the model reflections
40
+ # TODO: Turn into an enumerator
41
+ def each_reflection(holder)
42
+ skip_fields = skips_for(holder).map(&:to_s)
43
+ model._reflections.each_value do |reflection|
44
+ next if skip_fields.include?(reflection.name.to_s)
45
+
46
+ reflection = model._reflections[reflection.to_s] \
47
+ unless reflection.is_a?(abstract_reflection)
48
+
49
+ yield reflection unless reflection.nil?
50
+ end
51
+ end
52
+
53
+ protected
54
+
55
+ # Check if the given model is consider an interface due to single table
56
+ # inheritance and the given model is the base class
57
+ def sti_interface?
58
+ @sti_interface ||= begin
59
+ model.has_attribute?(model.inheritance_column) && model.base_class == model
60
+ end
61
+ end
62
+
63
+ # Build all enums associated to the class, collecting them from the
64
+ # model setting
65
+ def build_enum_types
66
+ return remove_instance_variable(:@enums) if enums.blank?
67
+
68
+ @enums = enums.map do |attribute, setting|
69
+ [attribute.to_s, create_enum(attribute.to_s, setting, once: true)]
70
+ rescue DuplicatedError
71
+ next
72
+ end.compact.to_h.freeze
73
+ end
74
+
75
+ # Build all necessary attribute fields into the given +holder+
76
+ def build_attribute_fields(holder, **field_options)
77
+ attributes_as_ids = reflection_attributes(holder)
78
+ each_attribute(holder) do |key, type, **options|
79
+ next if skip.include?(key) || holder.field?(key)
80
+
81
+ str_key = key.to_s
82
+ type = (defined?(@enums) && @enums.key?(str_key) && @enums[str_key]) ||
83
+ (attributes_as_ids.include?(str_key) && :id) || type
84
+
85
+ options[:null] = !attr_required?(key) unless options.key?(:null)
86
+ holder.field(key, type, **options.merge(field_options[key] || {}))
87
+ end
88
+ end
89
+
90
+ # Build all necessary reflection fields into the given +holder+
91
+ def build_reflection_fields(holder)
92
+ each_reflection(holder) do |item|
93
+ next if holder.field?(item.name)
94
+ type_map_after_register(item.klass.name) do |type|
95
+ next unless (type.object? && type.try(:assigned_to) != item.klass) ||
96
+ type.interface?
97
+
98
+ options = reflection_to_options(item)
99
+
100
+ if type <= Source::ActiveRecordSource
101
+ source_name = item.collection? ? type.plural : type.singular
102
+ proxy_options = options.merge(alias: reflection.name, of_type: :proxy)
103
+
104
+ if (source = type.query_fields[source_name]).present?
105
+ field = holder.safe_field(source, **proxy_options)
106
+ end
107
+ end
108
+
109
+ field ||= holder.field(item.name, type, **options)
110
+ field.before_resolve(:preload_association, item.name)
111
+ field.before_resolve(:build_association_scope, item.name)
112
+ field.resolve(:parent_owned_records, item.collection?)
113
+ end
114
+ end
115
+ end
116
+
117
+ # Build all +accepts_nested_attributes_for+ inside the input object
118
+ def build_reflection_inputs(holder)
119
+ model.nested_attributes_options.each_key do |reflection_name|
120
+ next if (reflection = model._reflect_on_association(reflection_name)).nil?
121
+
122
+ expected_name = reflection.klass.name.tr(':', '')
123
+ expected_name += 'Input' unless expected_name.ends_with?('Input')
124
+
125
+ type_map_after_register(expected_name) do |input|
126
+ options = reflection_to_options(reflection).merge(null: true)
127
+ field_name = "#{reflection.name}_attributes"
128
+ holder.safe_field(field_name, input, **options)
129
+ end
130
+ end
131
+ end
132
+
133
+ # Transform a replection into a field options
134
+ def reflection_to_options(reflection)
135
+ options = { array: reflection.collection? }
136
+
137
+ required = options[:array]
138
+ required ||= attr_required?(reflection.name)
139
+ required ||= attr_required?(reflection.association_foreign_key) \
140
+ if reflection.belongs_to?
141
+
142
+ options[:nullable] = !options[:array]
143
+ options[:null] = !required
144
+ options
145
+ end
146
+
147
+ private
148
+
149
+ def abstract_reflection # :nodoc:
150
+ ::ActiveRecord::Reflection::AbstractReflection
151
+ end
152
+ end
153
+ end
154
+ end