rails-graphql 0.1.0

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 (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