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