lighthouse-graphql 0.1.1 → 0.2.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 +4 -4
- data/CHANGELOG.md +154 -1
- data/README.md +42 -2
- 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/mutations.md +260 -0
- data/docs/performance.md +138 -0
- data/docs/relationships.md +80 -33
- data/docs/resolvers.md +179 -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
- metadata +51 -63
- 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
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@model_class = model_class
|
|
9
|
-
end
|
|
3
|
+
class Lighthouse::GraphQL::DataloaderSources::ModelLoader < GraphQL::Dataloader::Source
|
|
4
|
+
def initialize(model_class)
|
|
5
|
+
super()
|
|
6
|
+
@model_class = model_class
|
|
7
|
+
end
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
end
|
|
17
|
-
end
|
|
9
|
+
# Keys are ids. GraphQL `ID` arguments arrive as strings while records
|
|
10
|
+
# carry their column type (usually Integer), so match on the string form.
|
|
11
|
+
def fetch(keys)
|
|
12
|
+
records = @model_class.where(id: keys).index_by { |record| record.id.to_s }
|
|
13
|
+
keys.map { |k| records[k.to_s] }
|
|
18
14
|
end
|
|
19
15
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The total of a paginated relation for every parent the Dataloader collected,
|
|
4
|
+
# in one GROUP BY query. Only asked for when the client selects a total.
|
|
5
|
+
class Lighthouse::GraphQL::DataloaderSources::RelationCountLoader < GraphQL::Dataloader::Source
|
|
6
|
+
def initialize(batch)
|
|
7
|
+
super()
|
|
8
|
+
@batch = batch
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# keys: the parent records
|
|
12
|
+
def fetch(owners)
|
|
13
|
+
counts = @batch.counts(owners)
|
|
14
|
+
owners.map { |owner| counts.fetch(@batch.owner_key(owner), 0) }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# One page of a paginated relation for every parent the Dataloader collected,
|
|
4
|
+
# in one query (see Lighthouse::GraphQL::RelationBatch).
|
|
5
|
+
class Lighthouse::GraphQL::DataloaderSources::RelationPageLoader < GraphQL::Dataloader::Source
|
|
6
|
+
def initialize(batch, offset, limit)
|
|
7
|
+
super()
|
|
8
|
+
@batch = batch
|
|
9
|
+
@offset = offset
|
|
10
|
+
@limit = limit
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# keys: the parent records
|
|
14
|
+
def fetch(owners)
|
|
15
|
+
pages = @batch.pages(owners, @offset, @limit)
|
|
16
|
+
owners.map { |owner| pages.fetch(@batch.owner_key(owner), []) }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -3,84 +3,72 @@
|
|
|
3
3
|
require_relative 'base'
|
|
4
4
|
require_relative 'builder_applier'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
module DirectiveResolvers
|
|
9
|
-
class AggregateDirectiveResolver < BaseDirectiveResolver
|
|
10
|
-
def define_resolver(target_type_class)
|
|
11
|
-
field_name = @field_def.name
|
|
12
|
-
method_name = "resolve_field_#{field_name}"
|
|
6
|
+
class Lighthouse::GraphQL::DirectiveResolvers::AggregateDirectiveResolver < Lighthouse::GraphQL::DirectiveResolvers::BaseDirectiveResolver
|
|
7
|
+
def self.directive_name = 'aggregate'
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
# An unknown function aggregates to null.
|
|
10
|
+
AGGREGATIONS = {
|
|
11
|
+
'SUM' => ->(relation, column) { relation.sum(column) },
|
|
12
|
+
'AVG' => ->(relation, column) { relation.average(column)&.to_f },
|
|
13
|
+
'MIN' => ->(relation, column) { relation.minimum(column) },
|
|
14
|
+
'MAX' => ->(relation, column) { relation.maximum(column) },
|
|
15
|
+
'COUNT' => ->(relation, _column) { relation.count }
|
|
16
|
+
}.freeze
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
model_class ? model_class.all : nil
|
|
25
|
-
elsif relation_name && obj.respond_to?(relation_name)
|
|
26
|
-
obj.public_send(relation_name)
|
|
27
|
-
else
|
|
28
|
-
nil
|
|
29
|
-
end
|
|
18
|
+
def self.definition
|
|
19
|
+
'directive @aggregate(function: String!, column: String, model: String, relation: String) on FIELD_DEFINITION'
|
|
20
|
+
end
|
|
30
21
|
|
|
31
|
-
|
|
22
|
+
def define_resolver(target_type_class)
|
|
23
|
+
model_name = extract_directive_arg('model')
|
|
24
|
+
relation_name = extract_directive_arg('relation')
|
|
25
|
+
column_name = extract_directive_arg('column')
|
|
26
|
+
function_name = extract_directive_arg('function')
|
|
27
|
+
resolver_instance = self
|
|
28
|
+
field_ast_node = @field_def.ast_node
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
install(target_type_class, lambda do |obj, args, ctx|
|
|
31
|
+
relation = resolver_instance.send(:aggregate_source, obj, model_name, relation_name)
|
|
32
|
+
return nil unless relation
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
relation = Lighthouse::GraphQL::DirectiveResolvers::BuilderApplier.apply(relation, field_ast_node, args, ctx, resolver_instance)
|
|
35
|
+
resolver_instance.send(:compute_aggregation, relation, column_name, function_name)
|
|
36
|
+
end)
|
|
37
|
+
end
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
private
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
v = arg.value.respond_to?(:value) ? arg.value.value : arg.value
|
|
47
|
-
v.is_a?(::GraphQL::Language::Nodes::Enum) ? v.name : v
|
|
48
|
-
end
|
|
41
|
+
def extract_directive_arg(name)
|
|
42
|
+
arg = @directive_node.arguments&.find { |a| a.name == name }
|
|
43
|
+
return nil unless arg
|
|
49
44
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
case function_name.to_s.upcase
|
|
54
|
-
when 'SUM'
|
|
55
|
-
relation.sum(column)
|
|
56
|
-
when 'AVG'
|
|
57
|
-
relation.average(column)&.to_f
|
|
58
|
-
when 'MIN'
|
|
59
|
-
relation.minimum(column)
|
|
60
|
-
when 'MAX'
|
|
61
|
-
relation.maximum(column)
|
|
62
|
-
when 'COUNT'
|
|
63
|
-
relation.count
|
|
64
|
-
else
|
|
65
|
-
nil
|
|
66
|
-
end
|
|
67
|
-
rescue StandardError
|
|
68
|
-
nil
|
|
69
|
-
end
|
|
45
|
+
v = arg.value.respond_to?(:value) ? arg.value.value : arg.value
|
|
46
|
+
v.is_a?(::GraphQL::Language::Nodes::Enum) ? v.name : v
|
|
47
|
+
end
|
|
70
48
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
else
|
|
78
|
-
nil
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
end
|
|
49
|
+
# The model's table when `model:` is given, else the parent's relation.
|
|
50
|
+
def aggregate_source(obj, model_name, relation_name)
|
|
51
|
+
if model_name
|
|
52
|
+
constantize_model(model_name)&.all
|
|
53
|
+
elsif relation_name && obj.respond_to?(relation_name)
|
|
54
|
+
obj.public_send(relation_name)
|
|
84
55
|
end
|
|
85
56
|
end
|
|
57
|
+
|
|
58
|
+
def compute_aggregation(relation, column, function_name)
|
|
59
|
+
return nil unless relation.respond_to?(:any?)
|
|
60
|
+
|
|
61
|
+
AGGREGATIONS[function_name.to_s.upcase]&.call(relation, column.to_s)
|
|
62
|
+
rescue StandardError
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def install_resolve_proc(method_name)
|
|
67
|
+
return unless @field_def.respond_to?(:resolve_proc=)
|
|
68
|
+
|
|
69
|
+
@field_def.resolve_proc = lambda { |obj, arguments, ctx|
|
|
70
|
+
target_type_class = self.class
|
|
71
|
+
target_type_class.public_send(method_name, obj, arguments, ctx) if target_type_class.respond_to?(method_name)
|
|
72
|
+
}
|
|
73
|
+
end
|
|
86
74
|
end
|
|
@@ -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
|