rails-graphql 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +31 -0
- data/ext/depend +3 -0
- data/ext/extconf.rb +57 -0
- data/ext/graphqlparser/Ast.cpp +346 -0
- data/ext/graphqlparser/Ast.h +1214 -0
- data/ext/graphqlparser/AstNode.h +36 -0
- data/ext/graphqlparser/AstVisitor.h +137 -0
- data/ext/graphqlparser/GraphQLParser.cpp +76 -0
- data/ext/graphqlparser/GraphQLParser.h +55 -0
- data/ext/graphqlparser/JsonVisitor.cpp +161 -0
- data/ext/graphqlparser/JsonVisitor.cpp.inc +456 -0
- data/ext/graphqlparser/JsonVisitor.h +121 -0
- data/ext/graphqlparser/JsonVisitor.h.inc +110 -0
- data/ext/graphqlparser/VERSION +1 -0
- data/ext/graphqlparser/c/GraphQLAst.cpp +324 -0
- data/ext/graphqlparser/c/GraphQLAst.h +180 -0
- data/ext/graphqlparser/c/GraphQLAstForEachConcreteType.h +44 -0
- data/ext/graphqlparser/c/GraphQLAstNode.cpp +25 -0
- data/ext/graphqlparser/c/GraphQLAstNode.h +33 -0
- data/ext/graphqlparser/c/GraphQLAstToJSON.cpp +21 -0
- data/ext/graphqlparser/c/GraphQLAstToJSON.h +24 -0
- data/ext/graphqlparser/c/GraphQLAstVisitor.cpp +55 -0
- data/ext/graphqlparser/c/GraphQLAstVisitor.h +53 -0
- data/ext/graphqlparser/c/GraphQLParser.cpp +35 -0
- data/ext/graphqlparser/c/GraphQLParser.h +54 -0
- data/ext/graphqlparser/dump_json_ast.cpp +48 -0
- data/ext/graphqlparser/lexer.lpp +324 -0
- data/ext/graphqlparser/parser.ypp +693 -0
- data/ext/graphqlparser/parsergen/lexer.cpp +2633 -0
- data/ext/graphqlparser/parsergen/lexer.h +528 -0
- data/ext/graphqlparser/parsergen/location.hh +189 -0
- data/ext/graphqlparser/parsergen/parser.tab.cpp +3300 -0
- data/ext/graphqlparser/parsergen/parser.tab.hpp +646 -0
- data/ext/graphqlparser/parsergen/position.hh +179 -0
- data/ext/graphqlparser/parsergen/stack.hh +156 -0
- data/ext/graphqlparser/syntaxdefs.h +19 -0
- data/ext/libgraphqlparser/AstNode.h +36 -0
- data/ext/libgraphqlparser/CMakeLists.txt +148 -0
- data/ext/libgraphqlparser/CONTRIBUTING.md +23 -0
- data/ext/libgraphqlparser/GraphQLParser.cpp +76 -0
- data/ext/libgraphqlparser/GraphQLParser.h +55 -0
- data/ext/libgraphqlparser/JsonVisitor.cpp +161 -0
- data/ext/libgraphqlparser/JsonVisitor.h +121 -0
- data/ext/libgraphqlparser/LICENSE +22 -0
- data/ext/libgraphqlparser/README.clang-tidy +7 -0
- data/ext/libgraphqlparser/README.md +84 -0
- data/ext/libgraphqlparser/ast/ast.ast +203 -0
- data/ext/libgraphqlparser/ast/ast.py +61 -0
- data/ext/libgraphqlparser/ast/c.py +100 -0
- data/ext/libgraphqlparser/ast/c.pyc +0 -0
- data/ext/libgraphqlparser/ast/c_impl.py +61 -0
- data/ext/libgraphqlparser/ast/c_impl.pyc +0 -0
- data/ext/libgraphqlparser/ast/c_visitor_impl.py +39 -0
- data/ext/libgraphqlparser/ast/c_visitor_impl.pyc +0 -0
- data/ext/libgraphqlparser/ast/casing.py +26 -0
- data/ext/libgraphqlparser/ast/casing.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx.py +197 -0
- data/ext/libgraphqlparser/ast/cxx.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx_impl.py +61 -0
- data/ext/libgraphqlparser/ast/cxx_impl.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx_json_visitor_header.py +42 -0
- data/ext/libgraphqlparser/ast/cxx_json_visitor_header.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.py +80 -0
- data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.pyc +0 -0
- data/ext/libgraphqlparser/ast/cxx_visitor.py +64 -0
- data/ext/libgraphqlparser/ast/cxx_visitor.pyc +0 -0
- data/ext/libgraphqlparser/ast/js.py +65 -0
- data/ext/libgraphqlparser/ast/license.py +10 -0
- data/ext/libgraphqlparser/ast/license.pyc +0 -0
- data/ext/libgraphqlparser/c/GraphQLAstNode.cpp +25 -0
- data/ext/libgraphqlparser/c/GraphQLAstNode.h +33 -0
- data/ext/libgraphqlparser/c/GraphQLAstToJSON.cpp +21 -0
- data/ext/libgraphqlparser/c/GraphQLAstToJSON.h +24 -0
- data/ext/libgraphqlparser/c/GraphQLAstVisitor.cpp +55 -0
- data/ext/libgraphqlparser/c/GraphQLAstVisitor.h +53 -0
- data/ext/libgraphqlparser/c/GraphQLParser.cpp +35 -0
- data/ext/libgraphqlparser/c/GraphQLParser.h +54 -0
- data/ext/libgraphqlparser/clang-tidy-all.sh +3 -0
- data/ext/libgraphqlparser/cmake/version.cmake +16 -0
- data/ext/libgraphqlparser/dump_json_ast.cpp +48 -0
- data/ext/libgraphqlparser/go/README.md +20 -0
- data/ext/libgraphqlparser/go/callbacks.go +18 -0
- data/ext/libgraphqlparser/go/gotest.go +64 -0
- data/ext/libgraphqlparser/lexer.lpp +324 -0
- data/ext/libgraphqlparser/libgraphqlparser.pc.in +11 -0
- data/ext/libgraphqlparser/parser.ypp +693 -0
- data/ext/libgraphqlparser/parsergen/lexer.cpp +2633 -0
- data/ext/libgraphqlparser/parsergen/lexer.h +528 -0
- data/ext/libgraphqlparser/parsergen/location.hh +189 -0
- data/ext/libgraphqlparser/parsergen/parser.tab.cpp +3300 -0
- data/ext/libgraphqlparser/parsergen/parser.tab.hpp +646 -0
- data/ext/libgraphqlparser/parsergen/position.hh +179 -0
- data/ext/libgraphqlparser/parsergen/stack.hh +156 -0
- data/ext/libgraphqlparser/python/CMakeLists.txt +14 -0
- data/ext/libgraphqlparser/python/README.md +5 -0
- data/ext/libgraphqlparser/python/example.py +31 -0
- data/ext/libgraphqlparser/syntaxdefs.h +19 -0
- data/ext/libgraphqlparser/test/BuildCAPI.c +5 -0
- data/ext/libgraphqlparser/test/CMakeLists.txt +25 -0
- data/ext/libgraphqlparser/test/JsonVisitorTests.cpp +28 -0
- data/ext/libgraphqlparser/test/ParserTests.cpp +352 -0
- data/ext/libgraphqlparser/test/kitchen-sink.graphql +59 -0
- data/ext/libgraphqlparser/test/kitchen-sink.json +1 -0
- data/ext/libgraphqlparser/test/schema-kitchen-sink.graphql +78 -0
- data/ext/libgraphqlparser/test/schema-kitchen-sink.json +1 -0
- data/ext/libgraphqlparser/test/valgrind.supp +33 -0
- data/ext/version.cpp +21 -0
- data/lib/generators/graphql/controller_generator.rb +22 -0
- data/lib/generators/graphql/schema_generator.rb +22 -0
- data/lib/generators/graphql/templates/controller.erb +5 -0
- data/lib/generators/graphql/templates/schema.erb +6 -0
- data/lib/graphqlparser.so +0 -0
- data/lib/rails-graphql.rb +2 -0
- data/lib/rails/graphql.rake +1 -0
- data/lib/rails/graphql.rb +185 -0
- data/lib/rails/graphql/adapters/mysql_adapter.rb +0 -0
- data/lib/rails/graphql/adapters/pg_adapter.rb +50 -0
- data/lib/rails/graphql/adapters/sqlite_adapter.rb +39 -0
- data/lib/rails/graphql/argument.rb +220 -0
- data/lib/rails/graphql/callback.rb +124 -0
- data/lib/rails/graphql/collectors.rb +14 -0
- data/lib/rails/graphql/collectors/hash_collector.rb +83 -0
- data/lib/rails/graphql/collectors/idented_collector.rb +73 -0
- data/lib/rails/graphql/collectors/json_collector.rb +114 -0
- data/lib/rails/graphql/config.rb +61 -0
- data/lib/rails/graphql/directive.rb +203 -0
- data/lib/rails/graphql/directive/deprecated_directive.rb +59 -0
- data/lib/rails/graphql/directive/include_directive.rb +24 -0
- data/lib/rails/graphql/directive/skip_directive.rb +24 -0
- data/lib/rails/graphql/errors.rb +42 -0
- data/lib/rails/graphql/event.rb +141 -0
- data/lib/rails/graphql/field.rb +318 -0
- data/lib/rails/graphql/field/input_field.rb +92 -0
- data/lib/rails/graphql/field/mutation_field.rb +52 -0
- data/lib/rails/graphql/field/output_field.rb +96 -0
- data/lib/rails/graphql/field/proxied_field.rb +131 -0
- data/lib/rails/graphql/field/resolved_field.rb +96 -0
- data/lib/rails/graphql/field/scoped_config.rb +22 -0
- data/lib/rails/graphql/field/typed_field.rb +104 -0
- data/lib/rails/graphql/helpers.rb +40 -0
- data/lib/rails/graphql/helpers/attribute_delegator.rb +39 -0
- data/lib/rails/graphql/helpers/inherited_collection.rb +152 -0
- data/lib/rails/graphql/helpers/leaf_from_ar.rb +141 -0
- data/lib/rails/graphql/helpers/registerable.rb +103 -0
- data/lib/rails/graphql/helpers/with_arguments.rb +125 -0
- data/lib/rails/graphql/helpers/with_assignment.rb +113 -0
- data/lib/rails/graphql/helpers/with_callbacks.rb +55 -0
- data/lib/rails/graphql/helpers/with_directives.rb +126 -0
- data/lib/rails/graphql/helpers/with_events.rb +81 -0
- data/lib/rails/graphql/helpers/with_fields.rb +141 -0
- data/lib/rails/graphql/helpers/with_namespace.rb +40 -0
- data/lib/rails/graphql/helpers/with_owner.rb +35 -0
- data/lib/rails/graphql/helpers/with_schema_fields.rb +230 -0
- data/lib/rails/graphql/helpers/with_validator.rb +52 -0
- data/lib/rails/graphql/introspection.rb +53 -0
- data/lib/rails/graphql/native.rb +56 -0
- data/lib/rails/graphql/native/functions.rb +38 -0
- data/lib/rails/graphql/native/location.rb +41 -0
- data/lib/rails/graphql/native/pointers.rb +23 -0
- data/lib/rails/graphql/native/visitor.rb +349 -0
- data/lib/rails/graphql/railtie.rb +85 -0
- data/lib/rails/graphql/railties/base_generator.rb +35 -0
- data/lib/rails/graphql/railties/controller.rb +101 -0
- data/lib/rails/graphql/railties/controller_runtime.rb +40 -0
- data/lib/rails/graphql/railties/log_subscriber.rb +62 -0
- data/lib/rails/graphql/request.rb +343 -0
- data/lib/rails/graphql/request/arguments.rb +93 -0
- data/lib/rails/graphql/request/component.rb +100 -0
- data/lib/rails/graphql/request/component/field.rb +225 -0
- data/lib/rails/graphql/request/component/fragment.rb +118 -0
- data/lib/rails/graphql/request/component/operation.rb +178 -0
- data/lib/rails/graphql/request/component/operation/subscription.rb +16 -0
- data/lib/rails/graphql/request/component/spread.rb +119 -0
- data/lib/rails/graphql/request/component/typename.rb +82 -0
- data/lib/rails/graphql/request/context.rb +51 -0
- data/lib/rails/graphql/request/errors.rb +54 -0
- data/lib/rails/graphql/request/event.rb +112 -0
- data/lib/rails/graphql/request/helpers/directives.rb +64 -0
- data/lib/rails/graphql/request/helpers/selection_set.rb +87 -0
- data/lib/rails/graphql/request/helpers/value_writers.rb +115 -0
- data/lib/rails/graphql/request/steps/organizable.rb +146 -0
- data/lib/rails/graphql/request/steps/prepareable.rb +33 -0
- data/lib/rails/graphql/request/steps/resolveable.rb +32 -0
- data/lib/rails/graphql/request/strategy.rb +249 -0
- data/lib/rails/graphql/request/strategy/dynamic_instance.rb +41 -0
- data/lib/rails/graphql/request/strategy/multi_query_strategy.rb +36 -0
- data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +28 -0
- data/lib/rails/graphql/schema.rb +272 -0
- data/lib/rails/graphql/shortcuts.rb +77 -0
- data/lib/rails/graphql/source.rb +371 -0
- data/lib/rails/graphql/source/active_record/builders.rb +154 -0
- data/lib/rails/graphql/source/active_record_source.rb +231 -0
- data/lib/rails/graphql/source/scoped_arguments.rb +87 -0
- data/lib/rails/graphql/to_gql.rb +368 -0
- data/lib/rails/graphql/type.rb +138 -0
- data/lib/rails/graphql/type/enum.rb +206 -0
- data/lib/rails/graphql/type/enum/directive_location_enum.rb +30 -0
- data/lib/rails/graphql/type/enum/type_kind_enum.rb +57 -0
- data/lib/rails/graphql/type/input.rb +134 -0
- data/lib/rails/graphql/type/interface.rb +82 -0
- data/lib/rails/graphql/type/object.rb +111 -0
- data/lib/rails/graphql/type/object/directive_object.rb +34 -0
- data/lib/rails/graphql/type/object/enum_value_object.rb +25 -0
- data/lib/rails/graphql/type/object/field_object.rb +54 -0
- data/lib/rails/graphql/type/object/input_value_object.rb +49 -0
- data/lib/rails/graphql/type/object/schema_object.rb +40 -0
- data/lib/rails/graphql/type/object/type_object.rb +136 -0
- data/lib/rails/graphql/type/scalar.rb +71 -0
- data/lib/rails/graphql/type/scalar/bigint_scalar.rb +34 -0
- data/lib/rails/graphql/type/scalar/binary_scalar.rb +30 -0
- data/lib/rails/graphql/type/scalar/boolean_scalar.rb +37 -0
- data/lib/rails/graphql/type/scalar/date_scalar.rb +34 -0
- data/lib/rails/graphql/type/scalar/date_time_scalar.rb +32 -0
- data/lib/rails/graphql/type/scalar/decimal_scalar.rb +35 -0
- data/lib/rails/graphql/type/scalar/float_scalar.rb +32 -0
- data/lib/rails/graphql/type/scalar/id_scalar.rb +39 -0
- data/lib/rails/graphql/type/scalar/int_scalar.rb +36 -0
- data/lib/rails/graphql/type/scalar/string_scalar.rb +28 -0
- data/lib/rails/graphql/type/scalar/time_scalar.rb +40 -0
- data/lib/rails/graphql/type/union.rb +87 -0
- data/lib/rails/graphql/type_map.rb +347 -0
- data/lib/rails/graphql/version.rb +7 -0
- data/test/assets/introspection-db.json +0 -0
- data/test/assets/introspection-mem.txt +1 -0
- data/test/assets/introspection.gql +91 -0
- data/test/assets/luke.jpg +0 -0
- data/test/assets/mem.gql +428 -0
- data/test/assets/sqlite.gql +423 -0
- data/test/config.rb +80 -0
- data/test/graphql/request/context_test.rb +70 -0
- data/test/graphql/schema_test.rb +190 -0
- data/test/graphql/source_test.rb +237 -0
- data/test/graphql/type/enum_test.rb +203 -0
- data/test/graphql/type/input_test.rb +138 -0
- data/test/graphql/type/interface_test.rb +72 -0
- data/test/graphql/type/object_test.rb +104 -0
- data/test/graphql/type/scalar/bigint_scalar_test.rb +42 -0
- data/test/graphql/type/scalar/binary_scalar_test.rb +17 -0
- data/test/graphql/type/scalar/boolean_scalar_test.rb +40 -0
- data/test/graphql/type/scalar/date_scalar_test.rb +29 -0
- data/test/graphql/type/scalar/date_time_scalar_test.rb +29 -0
- data/test/graphql/type/scalar/decimal_scalar_test.rb +28 -0
- data/test/graphql/type/scalar/float_scalar_test.rb +22 -0
- data/test/graphql/type/scalar/id_scalar_test.rb +26 -0
- data/test/graphql/type/scalar/int_scalar_test.rb +26 -0
- data/test/graphql/type/scalar/string_scalar_test.rb +17 -0
- data/test/graphql/type/scalar/time_scalar_test.rb +36 -0
- data/test/graphql/type/scalar_test.rb +45 -0
- data/test/graphql/type/union_test.rb +82 -0
- data/test/graphql/type_map_test.rb +362 -0
- data/test/graphql/type_test.rb +68 -0
- data/test/graphql_test.rb +55 -0
- data/test/integration/config.rb +56 -0
- data/test/integration/memory/star_wars_introspection_test.rb +144 -0
- data/test/integration/memory/star_wars_query_test.rb +184 -0
- data/test/integration/memory/star_wars_validation_test.rb +99 -0
- data/test/integration/schemas/memory.rb +232 -0
- data/test/integration/schemas/sqlite.rb +82 -0
- data/test/integration/sqlite/star_wars_introspection_test.rb +15 -0
- data/test/integration/sqlite/star_wars_mutation_test.rb +82 -0
- data/test/integration/sqlite/star_wars_query_test.rb +71 -0
- data/test/test_ext.rb +48 -0
- 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
|