lighthouse-graphql 0.1.1 → 0.2.1
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 +4 -4
- data/CHANGELOG.md +175 -1
- data/README.md +47 -3
- data/docs/authorization.md +116 -57
- data/docs/best-practices.md +16 -3
- data/docs/caching.md +161 -0
- data/docs/configuration.md +83 -4
- data/docs/custom-directives.md +5 -1
- data/docs/directives.md +117 -38
- data/docs/getting-started.md +84 -0
- data/docs/motivation.md +184 -0
- data/docs/mutations.md +260 -0
- data/docs/performance.md +138 -0
- data/docs/relationships.md +80 -33
- data/docs/resolvers.md +189 -0
- data/docs/validation.md +132 -0
- data/lib/generators/lighthouse/builder_generator.rb +30 -0
- data/lib/generators/lighthouse/class_generator.rb +61 -0
- data/lib/generators/lighthouse/install/install_generator.rb +127 -0
- data/lib/generators/lighthouse/install/templates/graphql.config.yml.tt +8 -0
- data/lib/generators/lighthouse/install/templates/graphql_controller.rb.tt +57 -0
- data/lib/generators/lighthouse/install/templates/initializer.rb.tt +67 -0
- data/lib/generators/lighthouse/install/templates/schema.graphql.tt +19 -0
- data/lib/generators/lighthouse/install/templates/schema.rb.tt +10 -0
- data/lib/generators/lighthouse/mutation_generator.rb +30 -0
- data/lib/generators/lighthouse/query_generator.rb +29 -0
- data/lib/generators/lighthouse/validator_generator.rb +36 -0
- data/lib/lighthouse/cache_control.rb +126 -0
- data/lib/lighthouse/caching.rb +68 -0
- data/lib/lighthouse/config.rb +85 -19
- data/lib/lighthouse/contracts/arg_builder.rb +8 -12
- data/lib/lighthouse/contracts/arg_manipulator.rb +8 -12
- data/lib/lighthouse/contracts/directive.rb +10 -14
- data/lib/lighthouse/contracts/field_manipulator.rb +8 -12
- data/lib/lighthouse/contracts/field_middleware.rb +7 -11
- data/lib/lighthouse/contracts/field_resolver.rb +9 -13
- data/lib/lighthouse/dataloader_sources/association_loader.rb +28 -19
- data/lib/lighthouse/dataloader_sources/model_loader.rb +10 -14
- data/lib/lighthouse/dataloader_sources/relation_count_loader.rb +16 -0
- data/lib/lighthouse/dataloader_sources/relation_page_loader.rb +18 -0
- data/lib/lighthouse/directive_resolvers/aggregate_resolver.rb +57 -69
- data/lib/lighthouse/directive_resolvers/all_resolver.rb +26 -57
- data/lib/lighthouse/directive_resolvers/association_resolvers.rb +161 -0
- data/lib/lighthouse/directive_resolvers/auth_resolver.rb +41 -38
- data/lib/lighthouse/directive_resolvers/base.rb +142 -86
- data/lib/lighthouse/directive_resolvers/builder_applier.rb +58 -56
- data/lib/lighthouse/directive_resolvers/cache_resolvers.rb +133 -0
- data/lib/lighthouse/directive_resolvers/can_resolvers.rb +177 -0
- data/lib/lighthouse/directive_resolvers/convention_resolver.rb +70 -0
- data/lib/lighthouse/directive_resolvers/count_resolver.rb +42 -48
- data/lib/lighthouse/directive_resolvers/field_resolver.rb +27 -68
- data/lib/lighthouse/directive_resolvers/find_resolver.rb +42 -52
- data/lib/lighthouse/directive_resolvers/first_resolver.rb +39 -45
- data/lib/lighthouse/directive_resolvers/guard_resolver.rb +16 -22
- data/lib/lighthouse/directive_resolvers/method_resolver.rb +19 -25
- data/lib/lighthouse/directive_resolvers/model_mutation_resolver.rb +219 -0
- data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +70 -128
- data/lib/lighthouse/directive_resolvers/rename_resolver.rb +18 -24
- data/lib/lighthouse/directive_resolvers/resolution.rb +31 -27
- data/lib/lighthouse/directive_resolvers/validator_resolver.rb +193 -0
- data/lib/lighthouse/directive_resolvers/where_conditions_applier.rb +164 -163
- data/lib/lighthouse/directives/arguments/base.rb +23 -31
- data/lib/lighthouse/directives/arguments/filters.rb +113 -93
- data/lib/lighthouse/directives/arguments/order_by.rb +41 -41
- data/lib/lighthouse/directives/pagination_info_types.rb +69 -0
- data/lib/lighthouse/directives/pagination_manipulator.rb +176 -0
- data/lib/lighthouse/directives/registry.rb +210 -141
- data/lib/lighthouse/directives/where_conditions_directive.rb +236 -200
- data/lib/lighthouse/errors.rb +27 -0
- data/lib/lighthouse/federation_directives.rb +53 -0
- data/lib/lighthouse/field_resolver_stack.rb +56 -0
- data/lib/lighthouse/generated_resolvers.rb +82 -0
- data/lib/lighthouse/graphql/version.rb +1 -1
- data/lib/lighthouse/graphql_ruby_patches.rb +18 -0
- data/lib/lighthouse/ide_helper.rb +98 -0
- data/lib/lighthouse/namespaces.rb +21 -0
- data/lib/lighthouse/nested_mutations/belongs_to_operations.rb +50 -0
- data/lib/lighthouse/nested_mutations/executor.rb +149 -0
- data/lib/lighthouse/nested_mutations/has_operations.rb +66 -0
- data/lib/lighthouse/nested_mutations/many_to_many_operations.rb +44 -0
- data/lib/lighthouse/nested_mutations/partitioner.rb +125 -0
- data/lib/lighthouse/nested_mutations.rb +210 -0
- data/lib/lighthouse/pagination.rb +221 -0
- data/lib/lighthouse/persisted_queries.rb +167 -0
- data/lib/lighthouse/railtie.rb +26 -0
- data/lib/lighthouse/rb_lighthouse.rb +50 -29
- data/lib/lighthouse/reference_resolver.rb +13 -15
- data/lib/lighthouse/relation_batch.rb +156 -0
- data/lib/lighthouse/resolver_dispatch.rb +26 -0
- data/lib/lighthouse/schema_cache.rb +120 -0
- data/lib/lighthouse/schema_factory.rb +27 -37
- data/lib/lighthouse/schema_generator.rb +196 -239
- data/lib/lighthouse/schema_implementation.rb +51 -48
- data/lib/lighthouse/sdl_loader.rb +17 -20
- data/lib/lighthouse/support/arguments.rb +119 -0
- data/lib/lighthouse/support/authorization.rb +18 -19
- data/lib/lighthouse/support/batching.rb +16 -0
- data/lib/lighthouse/support/directive_args.rb +26 -24
- data/lib/lighthouse/support/model_resolver.rb +56 -56
- data/lib/lighthouse/support/naming.rb +55 -59
- data/lib/lighthouse/tasks/lighthouse.rake +33 -0
- data/lib/lighthouse/where_conditions/operator_map.rb +25 -29
- data/sig/lighthouse/directives.rbs +158 -0
- data/sig/lighthouse/graphql.rbs +78 -0
- data/sig/lighthouse.rbs +142 -0
- metadata +56 -64
- data/lib/lighthouse/directive_resolvers/belongs_to_many_resolver.rb +0 -56
- data/lib/lighthouse/directive_resolvers/belongs_to_resolver.rb +0 -47
- data/lib/lighthouse/directive_resolvers/can_resolver.rb +0 -86
- data/lib/lighthouse/directive_resolvers/has_many_resolver.rb +0 -109
- data/lib/lighthouse/directive_resolvers/has_one_resolver.rb +0 -43
- data/lib/lighthouse/directives/relation_directive.rb +0 -332
|
@@ -3,71 +3,40 @@
|
|
|
3
3
|
require_relative 'base'
|
|
4
4
|
require_relative 'builder_applier'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class AllDirectiveResolver < BaseDirectiveResolver
|
|
10
|
-
def define_resolver(target_type_class)
|
|
11
|
-
field_name = @field_def.name
|
|
6
|
+
class Lighthouse::GraphQL::DirectiveResolvers::AllDirectiveResolver < Lighthouse::GraphQL::DirectiveResolvers::BaseDirectiveResolver
|
|
7
|
+
def self.directive_name = 'all'
|
|
8
|
+
def self.definition = 'directive @all(model: String, scopes: [String!]) on FIELD_DEFINITION'
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
def define_resolver(target_type_class)
|
|
11
|
+
model_class = constantize_model(extract_model_name || @field_def.type.unwrap.graphql_name)
|
|
12
|
+
me = self
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
target_type_class.define_singleton_method(method_name) do |_obj, arguments, ctx|
|
|
22
|
-
model_class = model_class_static
|
|
23
|
-
return [] unless model_class
|
|
24
|
-
|
|
25
|
-
# apply simple pagination args if present
|
|
26
|
-
args = arguments.respond_to?(:to_h) ? arguments.to_h : {}
|
|
27
|
-
relation = model_class.all
|
|
28
|
-
|
|
29
|
-
relation = DirectiveResolvers::BuilderApplier.apply(relation, field_ast_node, args, ctx, resolver_instance)
|
|
14
|
+
install(target_type_class, lambda do |_obj, arguments, ctx|
|
|
15
|
+
me.send(:resolve_all, model_class, arguments, ctx)
|
|
16
|
+
end)
|
|
17
|
+
end
|
|
30
18
|
|
|
31
|
-
|
|
32
|
-
relation = relation.limit(args[:first]) if args[:first]
|
|
33
|
-
relation = relation.offset(args[:offset]) if args[:offset]
|
|
34
|
-
relation
|
|
35
|
-
end
|
|
19
|
+
private
|
|
36
20
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
model_class = model_class_static
|
|
40
|
-
if model_class.nil?
|
|
41
|
-
[]
|
|
42
|
-
else
|
|
43
|
-
args = arguments.respond_to?(:to_h) ? arguments.to_h : {}
|
|
44
|
-
relation = model_class.all
|
|
21
|
+
def resolve_all(model_class, arguments, ctx)
|
|
22
|
+
return [] unless model_class
|
|
45
23
|
|
|
46
|
-
|
|
24
|
+
# apply simple pagination args if present
|
|
25
|
+
args = arguments.respond_to?(:to_h) ? arguments.to_h : {}
|
|
26
|
+
relation = apply_scopes(model_class.all)
|
|
47
27
|
|
|
48
|
-
|
|
49
|
-
relation = relation.limit(args[:first]) if args[:first]
|
|
50
|
-
relation = relation.offset(args[:offset]) if args[:offset]
|
|
51
|
-
relation
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
28
|
+
relation = Lighthouse::GraphQL::DirectiveResolvers::BuilderApplier.apply(relation, @field_def.ast_node, args, ctx, self)
|
|
56
29
|
|
|
57
|
-
|
|
30
|
+
relation = apply_where_filters(relation, args)
|
|
31
|
+
relation = relation.limit(args[:first]) if args[:first]
|
|
32
|
+
relation = relation.offset(args[:offset]) if args[:offset]
|
|
33
|
+
relation
|
|
34
|
+
end
|
|
58
35
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
arg.value.respond_to?(:value) ? arg.value.value : arg.value
|
|
63
|
-
end
|
|
36
|
+
def extract_model_name
|
|
37
|
+
arg = @directive_node.arguments&.find { |a| a.name == 'model' }
|
|
38
|
+
return nil unless arg
|
|
64
39
|
|
|
65
|
-
|
|
66
|
-
return nil unless name
|
|
67
|
-
name = name.to_s.split('::').last
|
|
68
|
-
Object.const_get(name) rescue nil
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
40
|
+
arg.value.respond_to?(:value) ? arg.value.value : arg.value
|
|
72
41
|
end
|
|
73
42
|
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Lighthouse::GraphQL::DirectiveResolvers
|
|
6
|
+
# Relationship directives: resolve an ActiveRecord association of the
|
|
7
|
+
# parent. `relation:` names it (default: the field name in snake_case);
|
|
8
|
+
# `scopes:` applies model scopes. The Morph* directives are PHP Lighthouse's
|
|
9
|
+
# names for polymorphic associations, which ActiveRecord resolves the same way.
|
|
10
|
+
#
|
|
11
|
+
# Without query constraints the association is batch-loaded through the
|
|
12
|
+
# Dataloader (one query per association per batch). Collection directives
|
|
13
|
+
# take PHP Lighthouse's `type:` — PAGINATOR, SIMPLE or CONNECTION — to
|
|
14
|
+
# paginate (see Directives::PaginationManipulator), and the field's argument
|
|
15
|
+
# filters (@eq, @where, @orderBy, ...) apply to the association's query.
|
|
16
|
+
# Pages are batched too: every parent's page in one query, and the totals
|
|
17
|
+
# in another (see RelationBatch).
|
|
18
|
+
class AssociationDirectiveResolver < Lighthouse::GraphQL::DirectiveResolvers::BaseDirectiveResolver
|
|
19
|
+
CONDITION_DIRECTIVES = %w[whereConditions whereHasConditions].freeze
|
|
20
|
+
|
|
21
|
+
def self.collection? = false
|
|
22
|
+
|
|
23
|
+
def define_resolver(target_type_class)
|
|
24
|
+
me = self
|
|
25
|
+
install(target_type_class, lambda do |obj, args, ctx|
|
|
26
|
+
next nil if obj.nil?
|
|
27
|
+
|
|
28
|
+
me.send(:resolve_association, obj, Lighthouse::Support::Arguments.shallow(args), ctx)
|
|
29
|
+
end)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
|
|
34
|
+
def resolve_association(obj, args, ctx)
|
|
35
|
+
name = association_name
|
|
36
|
+
return nil unless obj.respond_to?(name)
|
|
37
|
+
|
|
38
|
+
return paginate(obj, name, args, ctx) if pagination_type
|
|
39
|
+
return constrained(association_scope(obj, name), args) if constrained?(args)
|
|
40
|
+
|
|
41
|
+
dataloader = Lighthouse::Support::Batching.dataloader(ctx)
|
|
42
|
+
return obj.public_send(name) unless dataloader && obj.class.respond_to?(:reflect_on_association)
|
|
43
|
+
|
|
44
|
+
dataloader.with(Lighthouse::GraphQL::DataloaderSources::AssociationLoader, obj.class, name).load(obj)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def association_name
|
|
48
|
+
(directive_arg('relation') || Lighthouse::Support::Naming.attribute(@field_def.name)).to_s
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def pagination_type
|
|
52
|
+
self.class.collection? && directive_arg('type')&.to_s&.upcase
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Scopes or a supplied argument with a filter directive need a query of
|
|
56
|
+
# their own instead of the batched load.
|
|
57
|
+
def constrained?(args)
|
|
58
|
+
return true if directive_arg('scopes')
|
|
59
|
+
|
|
60
|
+
registry = Lighthouse::DirectiveRegistry.default
|
|
61
|
+
(@field_def.ast_node&.arguments || []).any? do |argument|
|
|
62
|
+
args.key?(argument.name.underscore.to_sym) && argument.directives.any? { |d| filter_directive?(registry, d) }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# An argument builder (@eq, @where, ...) or a where-conditions filter.
|
|
67
|
+
def filter_directive?(registry, directive)
|
|
68
|
+
registry.arg_builder_class(directive.name) || CONDITION_DIRECTIVES.include?(directive.name)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def constrained(relation, args)
|
|
72
|
+
relation = apply_where_filters(scoped(relation), args)
|
|
73
|
+
self.class.collection? ? relation : relation.first
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def paginate(obj, name, args, ctx)
|
|
77
|
+
limits = { default_count: directive_arg('defaultCount') }
|
|
78
|
+
limits[:max_count] = directive_arg('maxCount') || :unlimited if directive_given?('maxCount')
|
|
79
|
+
Lighthouse::GraphQL::Pagination.paginate(paginated_list(obj, name, args, ctx), pagination_type, args, limits)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# This parent's page, batched with its siblings' when the Dataloader can.
|
|
83
|
+
def paginated_list(obj, name, args, ctx)
|
|
84
|
+
dataloader = Lighthouse::Support::Batching.dataloader(ctx)
|
|
85
|
+
batch = dataloader && Lighthouse::GraphQL::RelationBatch.for(obj, name, args, self, ctx)
|
|
86
|
+
return Lighthouse::GraphQL::Pagination::BatchedList.new(batch, obj, dataloader) if batch
|
|
87
|
+
|
|
88
|
+
decorate_relation(association_scope(obj, name), args, ctx)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
public
|
|
92
|
+
|
|
93
|
+
# The field's scopes and filters on the association's query, for one
|
|
94
|
+
# parent or a RelationBatch of them.
|
|
95
|
+
def decorate_relation(relation, args, _ctx)
|
|
96
|
+
apply_where_filters(scoped(relation), args)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
protected
|
|
100
|
+
|
|
101
|
+
# The association as a query (for a singular association too), so scopes
|
|
102
|
+
# and filters can be applied to it.
|
|
103
|
+
def association_scope(obj, name)
|
|
104
|
+
obj.respond_to?(:association) ? obj.association(name.to_sym).scope : obj.public_send(name)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def scoped(relation)
|
|
108
|
+
Array(directive_arg('scopes')).reduce(relation) { |acc, scope| acc.public_send(scope) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def directive_given?(name)
|
|
112
|
+
@directive_node&.arguments&.any? { |a| a.name == name }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
COLLECTION_ARGUMENTS = 'relation: String, scopes: [String!], type: PaginateType, defaultCount: Int, maxCount: Int'
|
|
117
|
+
|
|
118
|
+
class HasManyDirectiveResolver < AssociationDirectiveResolver
|
|
119
|
+
def self.collection? = true
|
|
120
|
+
def self.directive_name = 'hasMany'
|
|
121
|
+
def self.definition = "directive @hasMany(#{COLLECTION_ARGUMENTS}) on FIELD_DEFINITION"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class BelongsToManyDirectiveResolver < AssociationDirectiveResolver
|
|
125
|
+
def self.collection? = true
|
|
126
|
+
def self.directive_name = 'belongsToMany'
|
|
127
|
+
def self.definition = "directive @belongsToMany(#{COLLECTION_ARGUMENTS}) on FIELD_DEFINITION"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
class MorphManyDirectiveResolver < AssociationDirectiveResolver
|
|
131
|
+
def self.collection? = true
|
|
132
|
+
def self.directive_name = 'morphMany'
|
|
133
|
+
def self.definition = "directive @morphMany(#{COLLECTION_ARGUMENTS}) on FIELD_DEFINITION"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
class MorphToManyDirectiveResolver < AssociationDirectiveResolver
|
|
137
|
+
def self.collection? = true
|
|
138
|
+
def self.directive_name = 'morphToMany'
|
|
139
|
+
def self.definition = "directive @morphToMany(#{COLLECTION_ARGUMENTS}) on FIELD_DEFINITION"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
class HasOneDirectiveResolver < AssociationDirectiveResolver
|
|
143
|
+
def self.directive_name = 'hasOne'
|
|
144
|
+
def self.definition = 'directive @hasOne(relation: String, scopes: [String!]) on FIELD_DEFINITION'
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
class MorphOneDirectiveResolver < AssociationDirectiveResolver
|
|
148
|
+
def self.directive_name = 'morphOne'
|
|
149
|
+
def self.definition = 'directive @morphOne(relation: String, scopes: [String!]) on FIELD_DEFINITION'
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
class BelongsToDirectiveResolver < AssociationDirectiveResolver
|
|
153
|
+
def self.directive_name = 'belongsTo'
|
|
154
|
+
def self.definition = 'directive @belongsTo(relation: String, scopes: [String!]) on FIELD_DEFINITION'
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
class MorphToDirectiveResolver < AssociationDirectiveResolver
|
|
158
|
+
def self.directive_name = 'morphTo'
|
|
159
|
+
def self.definition = 'directive @morphTo(relation: String, scopes: [String!]) on FIELD_DEFINITION'
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -1,44 +1,47 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
target_type_class.define_singleton_method(method_name, &wrapper)
|
|
38
|
-
@field_def.resolve_proc = wrapper if @field_def.respond_to?(:resolve_proc=)
|
|
39
|
-
end
|
|
3
|
+
class Lighthouse::GraphQL::DirectiveResolvers::AuthDirectiveResolver < Lighthouse::GraphQL::DirectiveResolvers::BaseDirectiveResolver
|
|
4
|
+
def self.directive_name = 'auth'
|
|
5
|
+
def self.definition = 'directive @auth on FIELD_DEFINITION'
|
|
6
|
+
|
|
7
|
+
def define_resolver(target_type_class)
|
|
8
|
+
inner = original_resolver(target_type_class)
|
|
9
|
+
|
|
10
|
+
install(target_type_class, lambda do |obj, arguments, ctx|
|
|
11
|
+
user = ctx && (ctx[:current_user] || ctx['current_user'])
|
|
12
|
+
raise(::GraphQL::ExecutionError, 'Unauthenticated.') unless user
|
|
13
|
+
|
|
14
|
+
inner.call(obj, arguments, ctx)
|
|
15
|
+
end)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# The field's resolver so far: an existing `resolve_field_*` method, the
|
|
21
|
+
# resolve_proc, or the parent's attribute reader.
|
|
22
|
+
def original_resolver(target_type_class)
|
|
23
|
+
field_name = @field_def.name
|
|
24
|
+
original_singleton =
|
|
25
|
+
(target_type_class.method(resolver_method_name) if target_type_class.respond_to?(resolver_method_name))
|
|
26
|
+
|
|
27
|
+
original_proc =
|
|
28
|
+
(@field_def.resolve_proc if @field_def.respond_to?(:resolve_proc))
|
|
29
|
+
|
|
30
|
+
lambda do |obj, arguments, ctx|
|
|
31
|
+
if original_singleton
|
|
32
|
+
original_singleton.call(obj, arguments, ctx)
|
|
33
|
+
elsif original_proc
|
|
34
|
+
original_proc.call(obj, arguments, ctx)
|
|
35
|
+
else
|
|
36
|
+
read_attribute(obj, field_name)
|
|
40
37
|
end
|
|
41
38
|
end
|
|
42
39
|
end
|
|
43
|
-
end
|
|
44
40
|
|
|
41
|
+
def read_attribute(obj, field_name)
|
|
42
|
+
return obj.public_send(field_name) if obj.respond_to?(field_name)
|
|
43
|
+
|
|
44
|
+
snake = field_name.to_s.underscore
|
|
45
|
+
obj.respond_to?(snake) ? obj.public_send(snake) : nil
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -1,91 +1,147 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
3
|
+
class Lighthouse::GraphQL::DirectiveResolvers::BaseDirectiveResolver
|
|
4
|
+
def initialize(field_def, type_def, directive_node, opts = {})
|
|
5
|
+
@field_def = field_def
|
|
6
|
+
@type_def = type_def
|
|
7
|
+
@directive_node = directive_node
|
|
8
|
+
@opts = opts
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def define_resolver(target_type_class)
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def apply_where_filters(relation, args)
|
|
16
|
+
return relation unless relation.respond_to?(:where)
|
|
17
|
+
|
|
18
|
+
args_h = args.respond_to?(:to_h) ? args.to_h : (args || {})
|
|
19
|
+
|
|
20
|
+
relation = apply_where_conditions(relation, args_h)
|
|
21
|
+
@field_def.ast_node.arguments.each do |arg_node|
|
|
22
|
+
relation = apply_where_has_conditions(relation, arg_node, args_h)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
apply_arg_builders(relation, args_h)
|
|
26
|
+
rescue ::GraphQL::ExecutionError
|
|
27
|
+
raise
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
Lighthouse::Config.handle_error(e)
|
|
30
|
+
relation
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Compose argument-builder directives (@eq, @neq, @in, @notIn, @like,
|
|
34
|
+
# @where, @orderBy) onto the relation, in SDL argument order, for each
|
|
35
|
+
# field argument that carries one and that the client supplied a value
|
|
36
|
+
# for.
|
|
37
|
+
def apply_arg_builders(relation, args_h)
|
|
38
|
+
return relation unless relation.respond_to?(:where)
|
|
39
|
+
return relation unless @field_def.ast_node
|
|
40
|
+
|
|
41
|
+
arg_builders.each do |arg_node, dnode, builder_class|
|
|
42
|
+
value = Lighthouse::Support::Naming.fetch_arg(args_h, arg_node.name)
|
|
43
|
+
next if value.nil?
|
|
44
|
+
|
|
45
|
+
relation = builder_class.new(arg_node, dnode).handle_builder(relation, value)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
relation
|
|
49
|
+
rescue ::GraphQL::ExecutionError
|
|
50
|
+
raise
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
Lighthouse::Config.handle_error(e)
|
|
53
|
+
relation
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def extract_relation_name_from_directive(arg_node)
|
|
57
|
+
directive = arg_node.directives.find { |dir| dir.name == 'whereHasConditions' }
|
|
58
|
+
return nil unless directive
|
|
59
|
+
|
|
60
|
+
relation_arg = directive.arguments.find { |arg| arg.name == 'relation' }
|
|
61
|
+
return nil unless relation_arg
|
|
62
|
+
|
|
63
|
+
relation_arg.value.respond_to?(:value) ? relation_arg.value.value : relation_arg.value
|
|
64
|
+
rescue StandardError
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def constantize_model(name)
|
|
69
|
+
Lighthouse::Support::ModelResolver.constantize(name)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The model scopes the directive's `scopes:` argument names, in order.
|
|
73
|
+
def apply_scopes(relation)
|
|
74
|
+
Array(directive_arg('scopes')).reduce(relation) { |acc, scope| acc.public_send(scope) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
protected
|
|
78
|
+
|
|
79
|
+
def directive_arg(name)
|
|
80
|
+
Lighthouse::Support::DirectiveArgs.literal(@directive_node, name)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def resolver_method_name
|
|
84
|
+
"resolve_field_#{@field_def.name}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The field's resolver so far, for a directive that wraps it.
|
|
88
|
+
def capture_inner(target_type_class)
|
|
89
|
+
Lighthouse::GraphQL::DirectiveResolvers::Resolution.capture(target_type_class, resolver_method_name, @field_def, @field_def.name)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def install(target_type_class, callable)
|
|
93
|
+
target_type_class.define_singleton_method(resolver_method_name, &callable)
|
|
94
|
+
@field_def.resolve_proc = callable if @field_def.respond_to?(:resolve_proc=)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The model a directive acts on: its `model:` argument, the `model:` of
|
|
98
|
+
# another directive on the field (`@count(model: "Post") @canQuery`), or
|
|
99
|
+
# the field's return type without a generated Paginator/Connection suffix.
|
|
100
|
+
def model_class
|
|
101
|
+
name = directive_arg('model') || sibling_model_name || return_type_model_name
|
|
102
|
+
constantize_model(name) || raise(Lighthouse::DefinitionError,
|
|
103
|
+
"@#{@directive_node&.name} on #{@type_def.graphql_name}.#{@field_def.name}: no model #{name}")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def sibling_model_name
|
|
109
|
+
(@field_def.ast_node&.directives || []).filter_map do |d|
|
|
110
|
+
Lighthouse::Support::DirectiveArgs.literal(d, 'model')
|
|
111
|
+
end.first
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def return_type_model_name
|
|
115
|
+
@field_def.type.unwrap.graphql_name.sub(/(SimplePaginator|Paginator|Connection)\z/, '')
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# The `where:` argument (@whereConditions), when the client supplied one.
|
|
119
|
+
def apply_where_conditions(relation, args_h)
|
|
120
|
+
where_condition = args_h[:where] || args_h['where']
|
|
121
|
+
return relation unless where_condition
|
|
122
|
+
|
|
123
|
+
Lighthouse::GraphQL::DirectiveResolvers::WhereConditionsApplier.apply(relation, where_condition)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# A `whereHasConditions` argument the client supplied filters the
|
|
127
|
+
# relation by the related records matching its condition.
|
|
128
|
+
def apply_where_has_conditions(relation, arg_node, args_h)
|
|
129
|
+
return relation unless arg_node.directives.any? { |dir| dir.name == 'whereHasConditions' }
|
|
130
|
+
|
|
131
|
+
arg_value = Lighthouse::Support::Naming.fetch_arg(args_h, arg_node.name)
|
|
132
|
+
return relation unless arg_value
|
|
133
|
+
|
|
134
|
+
relation_name = extract_relation_name_from_directive(arg_node) || Lighthouse::Support::Naming.derive_relation_name(arg_node.name)
|
|
135
|
+
Lighthouse::GraphQL::DirectiveResolvers::WhereConditionsApplier.apply_has_argument(relation, arg_node.name, arg_value, relation_name)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Every (argument, directive, builder class) on the field, in SDL order.
|
|
139
|
+
def arg_builders
|
|
140
|
+
registry = Lighthouse::DirectiveRegistry.default
|
|
141
|
+
(@field_def.ast_node.arguments || []).flat_map do |arg_node|
|
|
142
|
+
(arg_node.directives || []).filter_map do |dnode|
|
|
143
|
+
builder_class = registry.arg_builder_class(dnode.name)
|
|
144
|
+
[arg_node, dnode, builder_class] if builder_class
|
|
89
145
|
end
|
|
90
146
|
end
|
|
91
147
|
end
|