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,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ # = GraphQL Source Active Record
6
+ #
7
+ # This source allows the translation of active record objects into a new
8
+ # source object, creating:
9
+ # 1. 1 Object
10
+ # 2. 1 Input
11
+ # 3. 2 Query fields (ingular and plural)
12
+ # 4. 3 Mutation fields (create, update, destroy)
13
+ class Source::ActiveRecordSource < Source
14
+ include Source::ScopedArguments
15
+
16
+ require_relative 'active_record/builders'
17
+ extend Builders
18
+
19
+ validate_assignment('ActiveRecord::Base') do |value|
20
+ "The \"#{value.name}\" is not a valid Active Record model"
21
+ end
22
+
23
+ # The name of the class (or the class itself) to be used as superclass for
24
+ # the generate GraphQL interface type of this source
25
+ class_attribute :interface_class, instance_writer: false
26
+ alias interface interface_class
27
+
28
+ %i[object interface input].each do |type|
29
+ settings = { abstract: true, with_owner: true }
30
+ send("#{type}_class=", create_type(type, **settings))
31
+ end
32
+
33
+ self.abstract = true
34
+
35
+ delegate :primary_key, :singular, :plural, :model, to: :class
36
+
37
+ skip_on :input, :created_at, :updated_at
38
+
39
+ on :start do
40
+ GraphQL.enable_ar_adapter(adapter_name)
41
+ build_enum_types
42
+ end
43
+
44
+ on :object do
45
+ build_attribute_fields(self)
46
+ build_reflection_fields(self)
47
+ end
48
+
49
+ on :input do
50
+ extra = { primary_key => { null: true } }
51
+ build_attribute_fields(self, **extra)
52
+ build_reflection_inputs(self)
53
+
54
+ safe_field(model.inheritance_column, :string, null: false) if object.interface?
55
+ safe_field(:_delete, :boolean, default: false)
56
+
57
+ reference = model.new
58
+ model.columns_hash.each_value do |column|
59
+ change_field(column.name, default: reference[column.name]) \
60
+ if column.default.present? && field?(column.name)
61
+ end
62
+ end
63
+
64
+ on :query do
65
+ safe_field(plural, object, full: true) do
66
+ before_resolve :load_records
67
+ end
68
+
69
+ safe_field(singular, object, null: false) do
70
+ argument primary_key, :id, null: false
71
+ before_resolve :load_record
72
+ end
73
+ end
74
+
75
+ on :mutation do
76
+ safe_field("create_#{singular}", object, null: false) do
77
+ argument singular, input, null: false
78
+ perform :create_record
79
+ end
80
+
81
+ safe_field("update_#{singular}", object, null: false) do
82
+ argument primary_key, :id, null: false
83
+ argument singular, input, null: false
84
+ before_resolve :load_record
85
+ perform :update_record
86
+ end
87
+
88
+ safe_field("delete_#{singular}", :boolean, null: false) do
89
+ argument primary_key, :id, null: false
90
+ before_resolve :load_record
91
+ perform :destroy_record
92
+ end
93
+ end
94
+
95
+ on :finish do
96
+ attach_fields!
97
+ attach_scoped_arguments_to(query_fields.values)
98
+ attach_scoped_arguments_to(mutation_fields.values)
99
+
100
+ next if model.base_class == model
101
+
102
+ # TODO: Allow nested inheritance for setting up implementation
103
+ type_map_after_register(model.base_class.name) do |type|
104
+ object.implements(type) if type.interface?
105
+ end
106
+ end
107
+
108
+ class << self
109
+ delegate :primary_key, :model_name, to: :model
110
+ delegate :singular, :plural, :param_key, to: :model_name
111
+ delegate :adapter_name, to: 'model.connection'
112
+
113
+ alias model assigned_class
114
+ alias model= assigned_to=
115
+
116
+ # Set the assignemnt to a model with a similar name as the source
117
+ def assigned_to
118
+ @assigned_to ||= name.delete_prefix('GraphQL::')[0..-7]
119
+ end
120
+
121
+ # Stores columns associated with enums so that the fields can have a
122
+ # correctly assigned type
123
+ def enums
124
+ @enums ||= model.defined_enums.dup
125
+ end
126
+
127
+ protected
128
+
129
+ # Check if a given +attr_name+ is associated with a presence validator
130
+ # but ignores when there is a default value
131
+ def attr_required?(attr_name)
132
+ return true if attr_name.eql?(primary_key)
133
+ return false if model.columns_hash[attr_name]&.default.present?
134
+ return false unless model._validators.key?(attr_name.to_sym)
135
+ model._validators[attr_name.to_sym].any?(presence_validator)
136
+ rescue ::ActiveRecord::StatementInvalid
137
+ false
138
+ end
139
+
140
+ private
141
+
142
+ def presence_validator # :nodoc:
143
+ ::ActiveRecord::Validations::PresenceValidator
144
+ end
145
+ end
146
+
147
+ # Prepare to load multiple records from the underlying table
148
+ def load_records
149
+ inject_scopes(model.all, :relation)
150
+ end
151
+
152
+ # Prepare to load a single record from the underlying table
153
+ def load_record
154
+ load_records.find(event.argument(primary_key))
155
+ end
156
+
157
+ # Get the chain result and preload the records with thre resulting scope
158
+ def preload_association(association, scope = nil)
159
+ event.stop(preload(association, scope || event.last_result), layer: :object)
160
+ end
161
+
162
+ # Collect a scope for filters applied to a given association
163
+ def build_association_scope(association)
164
+ scope = model._reflect_on_association(association).klass.unscoped
165
+
166
+ # Apply proxied injected scopes
167
+ proxied = event.field.try(:proxied_owner)
168
+ scope = event.on_instance(proxied) do |instance|
169
+ instance.inject_scopes(scope, :relation)
170
+ end if proxied.present? && proxied <= Source::ActiveRecordSource
171
+
172
+ # Apply self defined injected scopes
173
+ inject_scopes(scope, :relation)
174
+ end
175
+
176
+ # Once the records are pre-loaded due to +preload_association+, use the
177
+ # parent value and the preloader result to get the records
178
+ def parent_owned_records(collection_result = false)
179
+ data = event.data[:prepared]
180
+ return collection_result ? [] : nil unless data
181
+
182
+ result = data.records_by_owner[current_value] || []
183
+ collection_result ? result : result.first
184
+ end
185
+
186
+ # The perform step for the +create+ based mutation
187
+ def create_record
188
+ input_argument.resource.tap(&:save!)
189
+ end
190
+
191
+ # The perform step for the +update+ based mutation
192
+ def update_record
193
+ current_value.tap { |record| record.update!(**input_argument.params) }
194
+ end
195
+
196
+ # The perform step for the +delete+ based mutation
197
+ def destroy_record
198
+ !!current_value.destroy!
199
+ end
200
+
201
+ protected
202
+
203
+ # Basically get the argument associated to the input
204
+ def input_argument
205
+ event.argument(singular)
206
+ end
207
+
208
+ # Preload the records for a given +association+ using the current value.
209
+ # It can be further specified with a given +scope+
210
+ def preload(association, scope = nil)
211
+ reflection = model._reflect_on_association(association)
212
+ records = current_value.is_a?(preloader_association) \
213
+ ? current_value.preloaded_records \
214
+ : Array.wrap(current_value.itself).compact
215
+
216
+ ar_preloader.send(:preloaders_for_reflection, reflection, records, scope).first
217
+ end
218
+
219
+ # Get the cached instance of active record prelaoder
220
+ def ar_preloader
221
+ event.request.cache(:ar_preloader) { ::ActiveRecord::Associations::Preloader.new }
222
+ end
223
+
224
+ private
225
+
226
+ def preloader_association # :nodoc:
227
+ ActiveRecord::Associations::Preloader::Association
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails # :nodoc:
4
+ module GraphQL # :nodoc:
5
+ # This is a helper class that allows sources to have scoped-based arguments,
6
+ # meaning that when an argument is present, it triggers the underlying block
7
+ # on the fields where the argument was attached to
8
+ #
9
+ # TODO: Easy the usage of scoped arguments with AR, to map model scopes as
10
+ # arguments using the abilities here provided
11
+ module Source::ScopedArguments
12
+ def self.included(other)
13
+ other.extend(ClassMethods)
14
+ end
15
+
16
+ # Extended argument class to be the instance of the scoped
17
+ class Argument < GraphQL::Argument
18
+ attr_reader :block, :fields
19
+
20
+ def initialize(*args, on: nil, **xargs, &block)
21
+ super(*args, **xargs)
22
+
23
+ @block = block
24
+ @fields = Array.wrap(on).presence
25
+ end
26
+
27
+ # Check if the argument should be attached to the given +field+
28
+ def attach_to?(field)
29
+ return true if fields.nil?
30
+
31
+ fields.any? do |item|
32
+ (item.is_a?(Symbol) && field.name.eql?(item)) || field.gql_name.eql?(item)
33
+ end
34
+ end
35
+ end
36
+
37
+ module ClassMethods # :nodoc:
38
+ # Return the list of scoped params defined
39
+ def scoped_arguments
40
+ defined?(@scoped_arguments) ? @scoped_arguments : {}
41
+ end
42
+
43
+ protected
44
+
45
+ # Add a new scoped param to the list
46
+ def scoped_argument(param, type = :string, proc_method = nil, **settings, &block)
47
+ block = proc_method if proc_method.present? && block.nil?
48
+ argument = Argument.new(param, type, **settings.merge(owner: self), &block)
49
+ (@scoped_arguments ||= {})[argument.name] = argument
50
+ end
51
+
52
+ alias scoped_arg scoped_arguments
53
+
54
+ # Helper method to attach the scoped arguments to a given +field+
55
+ def attach_scoped_arguments_to(*fields, safe: true)
56
+ fields = fields.flatten.compact
57
+ return if fields.blank?
58
+
59
+ scoped_arguments.each_value do |argument|
60
+ fields.each do |item|
61
+ item.ref_argument(argument) if argument.attach_to?(item)
62
+ rescue DuplicatedError
63
+ raise unless safe
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ # Find all the executable arguments attached to the running field and
70
+ # call them with the given object
71
+ def inject_scopes(object, assigned_to = nil)
72
+ return object if event.field.nil? || (args_source = event.send(:args_source)).nil?
73
+
74
+ event.data[assigned_to] ||= object unless assigned_to.nil?
75
+ event.field.all_arguments.each_value.inject(object) do |result, argument|
76
+ next result unless argument.respond_to?(:block) && args_source.key?(argument.name)
77
+ send_args = argument.block.arity.eql?(1) ? [args_source[argument.name]] : []
78
+
79
+ value = result.instance_exec(*send_args, &argument.block)
80
+ value = value.nil? ? result : value
81
+
82
+ assigned_to.nil? ? value : event.data[assigned_to] = value
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,368 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'arel/visitors/visitor'
4
+
5
+ # rubocop:disable Naming/MethodParameterName, Naming/MethodName
6
+ module Rails # :nodoc:
7
+ module GraphQL # :nodoc:
8
+ # = GraphQL ToGQL
9
+ #
10
+ # This class can turn any class related to GraphQL into its GraphQL string
11
+ # representation. It was developed more for testing purposes, but it can
12
+ # also used by describing purposes or generating API description pages.
13
+ class ToGQL < Arel::Visitors::Visitor
14
+ DESCRIBE_TYPES = %i[scalar enum input_object interface union object].freeze
15
+
16
+ # Trigger a new compile process
17
+ def self.compile(node, **xargs)
18
+ new.compile(node, **xargs)
19
+ end
20
+
21
+ # Trigger a new describe process
22
+ def self.describe(schema, **xargs)
23
+ new.describe(schema, **xargs)
24
+ end
25
+
26
+ # Describe the given +node+ as GraphQL
27
+ def compile(node, collector = nil, with_descriptions: true)
28
+ collector ||= Collectors::IdentedCollector.new
29
+ @with_descriptions = with_descriptions
30
+ accept(node, collector).value
31
+ end
32
+
33
+ # Describe the given +schema+ as GraphQL, with all types and directives
34
+ def describe(schema, collector = nil, with_descriptions: true, with_spec: nil)
35
+ collector ||= Collectors::IdentedCollector.new
36
+ @with_descriptions = with_descriptions
37
+ @with_spec = with_spec.nil? ? schema.introspection? : with_spec
38
+
39
+ accept(schema, collector).eol
40
+
41
+ GraphQL.type_map.each_from(schema.namespace, base_class: :Type)
42
+ .group_by(&:kind).values_at(*DESCRIBE_TYPES)
43
+ .each do |items|
44
+ items&.sort_by(&:gql_name)&.each do |item|
45
+ next if !@with_spec && item.internal?
46
+
47
+ next visit_Rails_GraphQL_Type_Object(item, collector).eol \
48
+ if item.is_a?(::OpenStruct) && item.object?
49
+
50
+ accept(item, collector).eol
51
+ end
52
+ end
53
+
54
+ GraphQL.type_map.each_from(schema.namespace, base_class: :Directive)
55
+ .sort_by(&:gql_name).each do |item|
56
+ next if !@with_spec && item.spec_object
57
+ accept(item, collector).eol
58
+ end
59
+
60
+ collector.value.chomp
61
+ end
62
+
63
+ # Keep visitors in an alphabetical order, but leave instances at the end
64
+ protected
65
+
66
+ def visit_Rails_GraphQL_Directive(o, collector)
67
+ return visit_Rails_GraphQL_Directive_Instance(o, collector) \
68
+ unless o.is_a?(Module)
69
+
70
+ visit_description(o, collector)
71
+ collector << 'directive @' << o.gql_name
72
+ visit_arguments(o.arguments, collector)
73
+ collector << ' on '
74
+ collector << o.locations.map { |l| l.to_s.upcase }.join(' | ')
75
+ collector.eol
76
+ end
77
+
78
+ def visit_Rails_GraphQL_Field(o, collector)
79
+ return if o.disabled?
80
+
81
+ visit_description(o, collector)
82
+ collector << o.gql_name
83
+
84
+ visit_arguments(o.all_arguments, collector) \
85
+ if o.respond_to?(:all_arguments)
86
+
87
+ collector << ': '
88
+
89
+ visit_typed_object(o, collector)
90
+ visit_directives(o.directives, collector)
91
+ collector.eol if @with_descriptions
92
+ collector.eol
93
+ end
94
+
95
+ def visit_Rails_GraphQL_Mutation(o, collector)
96
+ visit_description(o, collector)
97
+ collector << o.gql_name
98
+
99
+ visit_arguments(o.arguments, collector)
100
+ collector << ': '
101
+
102
+ visit_typed_object(o, collector)
103
+ collector.eol
104
+ end
105
+
106
+ def visit_Rails_GraphQL_Schema(o, collector)
107
+ visit_description(o, collector)
108
+ collector << 'schema'
109
+ visit_directives(o.directives, collector)
110
+
111
+ collector.indented(' {', '}') do
112
+ Helpers::WithSchemaFields::SCHEMA_FIELD_TYPES.each do |key, name|
113
+ next unless key.eql?(:query) || o.fields_for(key).present?
114
+
115
+ collector << key.to_s
116
+ collector << ': '
117
+ collector << name
118
+ collector.eol
119
+ end
120
+ end
121
+ end
122
+
123
+ def visit_Rails_GraphQL_Field_AssociationField(o, collector)
124
+ return if o.disabled?
125
+
126
+ if @with_descriptions
127
+ field = o.instance_variable_get(:@field)
128
+ collector << '# Association of '
129
+ collector << field.owner.name
130
+ collector << '["'
131
+ collector << field.gql_name
132
+ collector << '"]'
133
+ collector.eol
134
+ end
135
+
136
+ visit_Rails_GraphQL_Field(o, collector)
137
+ end
138
+
139
+ def visit_Rails_GraphQL_Field_ProxyField(o, collector)
140
+ return if o.disabled?
141
+
142
+ if @with_descriptions
143
+ field = o.instance_variable_get(:@field)
144
+ collector << '# Proxy of '
145
+ collector << field.owner.name
146
+ collector << '["'
147
+ collector << field.gql_name
148
+ collector << '"]'
149
+ collector.eol
150
+ end
151
+
152
+ visit_Rails_GraphQL_Field(o, collector)
153
+ end
154
+
155
+ def visit_Rails_GraphQL_Field_OutputField(o, collector)
156
+ visit_Rails_GraphQL_Field(o, collector)
157
+ end
158
+
159
+ def visit_Rails_GraphQL_Field_InputField(o, collector)
160
+ visit_Rails_GraphQL_Field(o, collector)
161
+ end
162
+
163
+ def visit_Rails_GraphQL_Type_Enum(o, collector)
164
+ visit_description(o, collector)
165
+ collector << 'enum '
166
+ collector << o.gql_name
167
+ visit_directives(o.directives, collector)
168
+
169
+ collector.indented(' {', '}') do
170
+ o.values.each { |x| visit_enum_value(o, x, collector) }
171
+ end
172
+ end
173
+
174
+ def visit_Rails_GraphQL_Type_Input(o, collector)
175
+ visit_description(o, collector)
176
+ visit_assignment(o, collector)
177
+ collector << 'input '
178
+ collector << o.gql_name
179
+ visit_directives(o.directives, collector)
180
+
181
+ collector.indented(' {', '}') do
182
+ o.fields.each_value { |x| visit(x, collector) }
183
+ end
184
+ end
185
+
186
+ def visit_Rails_GraphQL_Type_Interface(o, collector)
187
+ visit_description(o, collector)
188
+ visit_assignment(o, collector)
189
+ collector << 'interface '
190
+ collector << o.gql_name
191
+ visit_directives(o.directives, collector)
192
+
193
+ collector.indented(' {', '}') do
194
+ o.fields.each_value { |x| visit(x, collector) }
195
+ end
196
+ end
197
+
198
+ def visit_Rails_GraphQL_Type_Object(o, collector)
199
+ visit_description(o, collector)
200
+ visit_assignment(o, collector)
201
+ collector << 'type '
202
+ collector << o.gql_name
203
+
204
+ if o.interfaces?
205
+ collector << ' implements '
206
+ o.all_interfaces.each_with_index do |x, i|
207
+ collector << ' & ' if i.positive?
208
+ collector << x.gql_name
209
+ end
210
+ end
211
+
212
+ visit_directives(o.directives, collector)
213
+
214
+ collector.indented(' {', '}') do
215
+ o.fields.each_value { |x| visit(x, collector) }
216
+ end
217
+ end
218
+
219
+ def visit_Rails_GraphQL_Type_Scalar(o, collector)
220
+ visit_description(o, collector)
221
+ collector << 'scalar '
222
+ collector << o.gql_name
223
+ visit_directives(o.directives, collector)
224
+
225
+ collector.eol
226
+ end
227
+
228
+ def visit_Rails_GraphQL_Type_Union(o, collector)
229
+ visit_description(o, collector)
230
+ collector << 'union '
231
+ collector << o.gql_name
232
+ visit_directives(o.directives, collector)
233
+
234
+ collector << ' = '
235
+ collector << o.members.map(&:gql_name).join(' | ')
236
+ collector.eol
237
+ end
238
+
239
+ def visit_Rails_GraphQL_Directive_Instance(o, collector)
240
+ collector << '@' << o.gql_name
241
+ return unless o.args.to_h.values.any?
242
+
243
+ collector << '('
244
+ o.arguments.values.each_with_index do |x, i|
245
+ value = o.args[x.gql_name]
246
+ value = o.args[x.name] if value.nil?
247
+ next if value.nil?
248
+
249
+ collector << ', ' if i.positive?
250
+ collector << x.gql_name
251
+ collector << ': '
252
+ collector << x.as_json(value).inspect
253
+ end
254
+ collector << ')'
255
+ end
256
+
257
+ def visit_Rails_GraphQL_Argument_Instance(o, collector)
258
+ visit_description(o, collector)
259
+ collector << o.gql_name << ': '
260
+ visit_typed_object(o, collector)
261
+ end
262
+
263
+ def visit_arguments(list, collector)
264
+ return if list.empty?
265
+
266
+ indented = @with_descriptions && list.values.any?(&:description?)
267
+
268
+ collector << '('
269
+ collector.eol.indent if indented
270
+
271
+ list.each_value.with_index do |x, i|
272
+ if i.positive?
273
+ collector << ', '
274
+ collector.eol if indented
275
+ collector.eol if @with_descriptions && x.description?
276
+ end
277
+
278
+ visit_Rails_GraphQL_Argument_Instance(x, collector)
279
+ end
280
+
281
+ collector.eol.unindent if indented
282
+ collector << ')'
283
+ end
284
+
285
+ def visit_assignment(o, collector)
286
+ return unless @with_descriptions && o.assigned?
287
+
288
+ collector << '# Assigned to '
289
+ collector << o.assigned_to
290
+ collector << ' class'
291
+ collector.eol
292
+ end
293
+
294
+ def visit_description(o, collector)
295
+ return unless @with_descriptions && o.description?
296
+
297
+ if o.description.lines.size === 1
298
+ collector << o.description.inspect
299
+ else
300
+ collector << '"""'
301
+ collector.eol
302
+
303
+ collector << o.description
304
+ collector.eol
305
+
306
+ collector << '"""'
307
+ end
308
+
309
+ collector.eol
310
+ end
311
+
312
+ def visit_directives(list, collector)
313
+ list&.each do |x|
314
+ collector << ' '
315
+ visit_Rails_GraphQL_Directive_Instance(x, collector)
316
+ end
317
+ end
318
+
319
+ def visit_enum_value(o, value, collector)
320
+ description = o.value_description[value]
321
+ directives = o.value_directives[value]
322
+
323
+ unless !@with_descriptions || description.nil?
324
+ collector << description.inspect
325
+ collector.eol
326
+ end
327
+
328
+ collector << value
329
+ visit_directives(directives, collector)
330
+ collector.eol if @with_descriptions
331
+ collector.eol
332
+ end
333
+
334
+ def visit_typed_object(o, collector)
335
+ collector << '[' if o.array?
336
+ collector << o.type_klass.gql_name
337
+
338
+ if o.array?
339
+ collector << '!' unless o.nullable?
340
+ collector << ']'
341
+ end
342
+
343
+ collector << '!' unless o.null?
344
+ collector << ' = ' << o.to_json(o.default) if o.try(:default_value?)
345
+ end
346
+
347
+ def visit(object, collector = nil) # :nodoc:
348
+ object_class = object.is_a?(Module) ? object : object.class
349
+ dispatch_method = dispatch[object_class]
350
+ if collector
351
+ send dispatch_method, object, collector
352
+ else
353
+ send dispatch_method, object
354
+ end
355
+ rescue ::NoMethodError => e
356
+ raise e if respond_to?(dispatch_method, true)
357
+ superklass = object_class.ancestors.find do |klass|
358
+ respond_to?(dispatch[klass], true)
359
+ end
360
+
361
+ raise(::TypeError, "Cannot visit #{object_class}") unless superklass
362
+ dispatch[object_class] = dispatch[superklass]
363
+ retry
364
+ end
365
+ end
366
+ end
367
+ end
368
+ # rubocop:enable Naming/MethodParameterName, Naming/MethodName