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,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ module Helpers # :nodoc:
6
+ # Helper module that allows other objects to hold fields during the
7
+ # definition process. Works very similar to Arguments, but it's more
8
+ # flexible, since the type of the fields can be dynamic defined by the
9
+ # class that extends this module.
10
+ #
11
+ # Fields, different from arguments, has extended types, which is somewhat
12
+ # related to the base type, but it's closer associated with the strategy
13
+ # used to handle them.
14
+ module WithFields
15
+ module ClassMethods # :nodoc: all
16
+ def inherited(subclass)
17
+ super if defined? super
18
+ return unless defined?(@fields)
19
+ fields.each_value(&subclass.method(:proxy_field))
20
+ end
21
+ end
22
+
23
+ def self.extended(other) # :nodoc:
24
+ other.extend(WithFields::ClassMethods)
25
+ other.define_singleton_method(:fields) { @fields ||= Concurrent::Map.new }
26
+ other.class_attribute(:field_type, instance_writer: false)
27
+ other.class_attribute(:valid_field_types, instance_writer: false, default: [])
28
+ end
29
+
30
+ # Check if the field is already defined before actually creating it
31
+ def safe_field(*args, of_type: nil, **xargs, &block)
32
+ method_name = of_type.nil? ? :field : "#{of_type}_field"
33
+ public_send(method_name, *args, **xargs, &block)
34
+ rescue DuplicatedError
35
+ # Do not do anything if it is duplicated
36
+ end
37
+
38
+ # See {Field}[rdoc-ref:Rails::GraphQL::Field] class.
39
+ def field(name, *args, **xargs, &block)
40
+ xargs[:owner] = self
41
+ object = field_type.new(name, *args, **xargs, &block)
42
+
43
+ raise DuplicatedError, <<~MSG.squish if field?(object.name)
44
+ The #{name.inspect} field is already defined and can't be redefined.
45
+ MSG
46
+
47
+ fields[object.name] = object
48
+ rescue DefinitionError => e
49
+ raise e.class, e.message + "\n Defined at: #{caller(2)[0]}"
50
+ end
51
+
52
+ # Add a new field to the list but use a proxy instead of a hard copy of
53
+ # a given +field+
54
+ def proxy_field(field, *args, **xargs, &block)
55
+ raise ArgumentError, <<~MSG.squish unless field.is_a?(GraphQL::Field)
56
+ The #{field.class.name} is not a valid field.
57
+ MSG
58
+
59
+ xargs[:owner] = self
60
+ object = field.to_proxy(*args, **xargs, &block)
61
+ raise DuplicatedError, <<~MSG.squish if field?(object.name)
62
+ The #{field.name.inspect} field is already defined and can't be replaced.
63
+ MSG
64
+
65
+ fields[object.name] = object
66
+ end
67
+
68
+ # Overwrite attributes of a given field named as +name+, it also allows
69
+ # a +block+ which will then further configure the field
70
+ def change_field(object, **xargs, &block)
71
+ find_field!(object).apply_changes(**xargs, &block)
72
+ end
73
+
74
+ alias overwrite_field change_field
75
+
76
+ # Perform extra configurations on a given +field+
77
+ def configure_field(object, &block)
78
+ find_field!(object).configure(&block)
79
+ end
80
+
81
+ # Disable a list of given +fields+
82
+ def disable_fields(*list)
83
+ list.flatten.map { |item| self[item]&.disable! }
84
+ end
85
+
86
+ # Enable a list of given +fields+
87
+ def enable_fields(*list)
88
+ list.flatten.map { |item| self[item]&.enable! }
89
+ end
90
+
91
+ # Check wheter a given field +object+ is defined in the list of fields
92
+ def field?(object)
93
+ object = object.name if object.is_a?(GraphQL::Field)
94
+ fields.key?(object.is_a?(String) ? object.underscore.to_sym : object)
95
+ end
96
+
97
+ # Allow accessing fields using the hash notation
98
+ def [](object)
99
+ object = object.name if object.is_a?(GraphQL::Field)
100
+ fields[object.is_a?(String) ? object.underscore.to_sym : object]
101
+ end
102
+
103
+ alias find_field []
104
+
105
+ # If the field is not found it will raise an exception
106
+ def find_field!(object)
107
+ find_field(object) || raise(::ArgumentError, <<~MSG.squish)
108
+ The #{object.inspect} field is not defined yet.
109
+ MSG
110
+ end
111
+
112
+ # Get the list of GraphQL names of all the fields difined
113
+ def field_names(enabled_only = true)
114
+ (enabled_only ? enabled_fields : fields.each_value).map(&:gql_name).compact
115
+ end
116
+
117
+ # Return a lazy enumerator for enabled fields
118
+ def enabled_fields
119
+ fields.values.select(&:enabled?)
120
+ end
121
+
122
+ # Validate all the fields to make sure the definition is valid
123
+ def validate!(*)
124
+ super if defined? super
125
+
126
+ # TODO: Maybe find a way to freeze the fields, since after validation
127
+ # the best thing to do is block changes
128
+ fields.each_value(&:validate!)
129
+ end
130
+
131
+ protected
132
+
133
+ # A little helper to define arguments using the :arguments key
134
+ def arg(*args, **xargs, &block)
135
+ xargs[:owner] = self
136
+ GraphQL::Argument.new(*args, **xargs, &block)
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ module Helpers # :nodoc:
6
+ # Helper module that allows other objects to hold namespaces. It can
7
+ # either work as an extension of the superclass using +add_namespace+ or
8
+ # it can be reset then set using +namespace+.
9
+ module WithNamespace
10
+ # Returns the list of defined namespaces
11
+ def namespaces
12
+ return @namespaces if defined?(@namespaces)
13
+ superclass.try(:namespaces) || Set.new
14
+ end
15
+
16
+ # Set or overwrite the list of namespaces
17
+ def set_namespace(*list)
18
+ @namespaces = normalize_namespaces(list).to_set
19
+ end
20
+
21
+ alias set_namespaces set_namespace
22
+
23
+ # Add more namespaces to the list already defined. If the super class
24
+ # has already defined namespaces, the extend that list.
25
+ def namespace(*list)
26
+ @namespaces = (superclass.try(:namespaces)&.dup || Set.new) \
27
+ unless defined?(@namespaces)
28
+
29
+ @namespaces.merge(normalize_namespaces(list))
30
+ end
31
+
32
+ private
33
+
34
+ def normalize_namespaces(list)
35
+ list.map { |item| item.to_s.underscore.to_sym }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ module Helpers # :nodoc:
6
+ # Helper module that allows other objects to hold an +assigned_to+ object
7
+ module WithOwner
8
+ def self.included(other)
9
+ other.class_attribute(:owner, instance_writer: false)
10
+ end
11
+
12
+ private
13
+
14
+ def respond_to_missing?(*args) # :nodoc:
15
+ owner_respond_to?(*args) || super
16
+ end
17
+
18
+ def method_missing(method_name, *args, **xargs, &block) # :nodoc:
19
+ return super unless owner_respond_to?(method_name)
20
+ event.on_instance(owner) do |obj|
21
+ obj.public_send(method_name, *args, **xargs, &block)
22
+ end
23
+ end
24
+
25
+ # Since owners are classes, this checks for the instance methods of
26
+ # it, since this is a instance method
27
+ def owner_respond_to?(method_name, include_private = false)
28
+ (include_private ? %i[public protected private] : %i[public]).any? do |type|
29
+ owner.send("#{type}_instance_methods").include?(method_name)
30
+ end unless owner.nil?
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ module Helpers # :nodoc:
6
+ # Helper module that allows other objects to hold schema fields (query,
7
+ # mutation, and subscription fields). Works very similar to fields, but
8
+ # they are placed in different places regarding their type.
9
+ module WithSchemaFields
10
+ SCHEMA_FIELD_TYPES = %i[query mutation subscription].map do |key|
11
+ [key, "_#{key.to_s.classify}"]
12
+ end.to_h.freeze
13
+
14
+ TYPE_FIELD_CLASS = {
15
+ query: 'OutputField',
16
+ mutation: 'MutationField',
17
+ subscription: 'OutputField',
18
+ }.freeze
19
+
20
+ module ClassMethods # :nodoc: all
21
+ def inherited(subclass)
22
+ super if defined? super
23
+
24
+ SCHEMA_FIELD_TYPES.each_key do |kind|
25
+ fields = instance_variable_defined?("@#{kind}_fields")
26
+ fields = fields ? instance_variable_get("@#{kind}_fields") : {}
27
+ fields.each_value { |field| subclass.add_proxy_field(kind, field) }
28
+ end
29
+ end
30
+ end
31
+
32
+ ScopedConfig = Struct.new(:source, :type) do # :nodoc: all
33
+ def arg(*args, **xargs, &block)
34
+ xargs[:owner] ||= source
35
+ GraphQL::Argument.new(*args, **xargs, &block)
36
+ end
37
+
38
+ private
39
+
40
+ def respond_to_missing?(method_name, include_private = false)
41
+ schema_methods.key?(method_name) ||
42
+ source.respond_to?(method_name, include_private) || super
43
+ end
44
+
45
+ def method_missing(method_name, *args, **xargs, &block)
46
+ schema_method = schema_methods[method_name]
47
+ args.unshift(type) unless schema_method.nil?
48
+ source.send(schema_method || method_name, *args, **xargs, &block)
49
+ end
50
+
51
+ def schema_methods
52
+ @@schema_methods ||= begin
53
+ typed_methods = WithSchemaFields.public_instance_methods
54
+ typed_methods = typed_methods.zip(typed_methods).to_h
55
+ typed_methods.merge(
56
+ fields: :fields_for,
57
+ safe_field: :safe_add_field,
58
+ field: :add_field,
59
+ proxy_field: :add_proxy_field,
60
+ field?: :has_field?,
61
+ )
62
+ end
63
+ end
64
+ end
65
+
66
+ def self.extended(other) # :nodoc:
67
+ other.extend(WithSchemaFields::ClassMethods)
68
+ end
69
+
70
+ # A little helper for getting the list of fields of a given type
71
+ def fields_for(type)
72
+ public_send("#{type}_fields")
73
+ end
74
+
75
+ # Return the object name for a given +type+ of list of fields
76
+ def type_name_for(type)
77
+ SCHEMA_FIELD_TYPES[type]
78
+ end
79
+
80
+ # Only add the field if it is not already defined
81
+ def safe_add_field(*args, of_type: nil, **xargs, &block)
82
+ method_name = of_type.nil? ? :add_field : "add_#{of_type}_field"
83
+ public_send(method_name, *args, **xargs, &block)
84
+ rescue DuplicatedError
85
+ # Do not do anything if it is duplicated
86
+ end
87
+
88
+ # Add a new field of the give +type+
89
+ # See {OutputField}[rdoc-ref:Rails::GraphQL::OutputField] class.
90
+ def add_field(type, *args, **xargs, &block)
91
+ xargs[:owner] = self
92
+ klass = Field.const_get(TYPE_FIELD_CLASS[type])
93
+ object = klass.new(*args, **xargs, &block)
94
+
95
+ raise DuplicatedError, <<~MSG.squish if has_field?(type, object.name)
96
+ The "#{object.name}" field is already defined on #{type} fields and
97
+ cannot be redefined.
98
+ MSG
99
+
100
+ fields_for(type)[object.name] = object
101
+ rescue DefinitionError => e
102
+ raise e.class, e.message + "\n Defined at: #{caller(2)[0]}"
103
+ end
104
+
105
+ # Add a new field to the list but use a proxy instead of a hard copy of
106
+ # a given +field+
107
+ def add_proxy_field(type, field, *args, **xargs, &block)
108
+ klass = Field.const_get(TYPE_FIELD_CLASS[type])
109
+ raise ArgumentError, <<~MSG.squish unless field.is_a?(klass)
110
+ The #{field.class.name} is not a valid field for #{type} fields.
111
+ MSG
112
+
113
+ xargs[:owner] = self
114
+ object = field.to_proxy(*args, **xargs, &block)
115
+ raise DuplicatedError, <<~MSG.squish if has_field?(type, object.name)
116
+ The #{field.name.inspect} field is already defined on #{type} fields
117
+ and cannot be replaced.
118
+ MSG
119
+
120
+ fields_for(type)[object.name] = object
121
+ end
122
+
123
+ # Find a field and then change some flexible attributes of it
124
+ def change_field(type, object, **xargs, &block)
125
+ find_field!(type, object).apply_changes(**xargs, &block)
126
+ end
127
+
128
+ alias overwrite_field change_field
129
+
130
+ # Run a configuration block for the given field of a given +type+
131
+ def configure_field(type, object, &block)
132
+ find_field!(type, object).configure(&block)
133
+ end
134
+
135
+ # Disable a list of given +fields+ from a given +type+
136
+ def disable_fields(type, *list)
137
+ list.flatten.map { |item| find_field(type, item)&.disable! }
138
+ end
139
+
140
+ # Enable a list of given +fields+ from a given +type+
141
+ def enable_fields(type, *list)
142
+ list.flatten.map { |item| find_field(type, item)&.enable! }
143
+ end
144
+
145
+ # Check if a field of the given +type+ exists. The +object+ can be the
146
+ # +gql_name+, +name+, or an actual field.
147
+ def has_field?(type, object)
148
+ object = object.name if object.is_a?(GraphQL::Field)
149
+ fields_for(type).key?(object.is_a?(String) ? object.underscore.to_sym : object)
150
+ end
151
+
152
+ # Find a specific field on the given +type+ list. The +object+ can be
153
+ # the +gql_name+, +name+, or an actual field.
154
+ def find_field(type, object)
155
+ object = object.name if object.is_a?(GraphQL::Field)
156
+ fields_for(type)[object.is_a?(String) ? object.underscore.to_sym : object]
157
+ end
158
+
159
+ # If the field is not found it will raise an exception
160
+ def find_field!(type, object)
161
+ find_field(type, object) || raise(::ArgumentError, <<~MSG.squish)
162
+ The #{object.inspect} field on #{type} is not defined yet.
163
+ MSG
164
+ end
165
+
166
+ # Get the list of GraphQL names of all the fields difined
167
+ def field_names_for(type, enabled_only = true)
168
+ (enabled_only ? fields_for(type).select(&:enabled?) : fields_for(type))
169
+ .map(&:gql_name).compact
170
+ end
171
+
172
+ # Run a configuration block for the given +type+
173
+ def configure_fields(type, &block)
174
+ schema_scoped_config(self, type).instance_exec(&block)
175
+ end
176
+
177
+ # Validate all the fields to make sure the definition is valid
178
+ def validate!(*)
179
+ super if defined? super
180
+
181
+ # TODO: Maybe find a way to freeze the fields, since after validation
182
+ # the best thing to do is block changes
183
+ SCHEMA_FIELD_TYPES.each_key do |kind|
184
+ next unless instance_variable_defined?("@#{kind}_fields")
185
+ instance_variable_get("@#{kind}_fields")&.each_value(&:validate!)
186
+ end
187
+ end
188
+
189
+ SCHEMA_FIELD_TYPES.each do |kind, type_name|
190
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
191
+ def #{kind}_field?(name)
192
+ field?(:#{kind}, name)
193
+ end
194
+
195
+ def #{kind}_fields(&block)
196
+ return @#{kind}_fields ||= Concurrent::Map.new if block.nil?
197
+ configure_fields(:#{kind}, &block)
198
+ @#{kind}_fields
199
+ end
200
+
201
+ def #{kind}_type_name
202
+ '#{type_name}'
203
+ end
204
+
205
+ def #{kind}_type
206
+ OpenStruct.new(
207
+ kind: :object,
208
+ object?: true,
209
+ kind_enum: 'OBJECT',
210
+ fields: defined?(@#{kind}_fields) ? @#{kind}_fields : nil,
211
+ gql_name: '#{type_name}',
212
+ interfaces: nil,
213
+ description: nil,
214
+ interfaces?: false,
215
+ internal?: false,
216
+ ).freeze
217
+ end
218
+ RUBY
219
+ end
220
+
221
+ protected
222
+
223
+ # Create a new instace of the +ScopedConfig+ class
224
+ def schema_scoped_config(*args)
225
+ WithSchemaFields::ScopedConfig.new(*args)
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ module Helpers # :nodoc:
6
+ # Helper that contains the main exceptions and validation process for a
7
+ # value against a type
8
+ module WithValidator
9
+ delegate :ordinalize, to: 'ActiveSupport::Inflector'
10
+
11
+ protected
12
+
13
+ # Run the validation process with +value+ against +type+
14
+ def validate_output!(value, type, checker: :null?, array: true)
15
+ result = validate_null(value, checker)
16
+ result ||= array? && array \
17
+ ? validate_array(value) \
18
+ : validate_type(value) \
19
+ unless value.nil?
20
+
21
+ return if result.blank?
22
+ message, idx = result
23
+
24
+ base_error = idx.present? \
25
+ ? "#{ordinalize(idx + 1)} value of the #{gql_name} #{type}" \
26
+ : "#{gql_name} #{type} value"
27
+
28
+ raise InvalidValueError, "The #{base_error} #{message}."
29
+ end
30
+
31
+ private
32
+
33
+ def validate_array(value) # :nodoc:
34
+ return 'is not an array' unless value.is_a?(Enumerable)
35
+
36
+ value.each_with_index do |val, idx|
37
+ err = validate_null(val, :nullable?) || validate_type(val)
38
+ return err, idx unless err.nil?
39
+ end
40
+ end
41
+
42
+ def validate_null(value, checker = :null?) # :nodoc:
43
+ 'can not be null' if value.nil? && !send(checker)
44
+ end
45
+
46
+ def validate_type(value) # :nodoc:
47
+ 'is invalid' if leaf_type? && !type_klass.valid_output?(value)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end