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
|
@@ -1,168 +1,169 @@
|
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
3
|
+
class Lighthouse::GraphQL::DirectiveResolvers::WhereConditionsApplier
|
|
4
|
+
# Operator semantics live in Lighthouse::WhereConditions::OperatorMap so
|
|
5
|
+
# they can be shared with the simple argument-filter directives.
|
|
6
|
+
SUPPORTED_OPERATORS = Lighthouse::WhereConditions::OperatorMap::OPERATORS
|
|
7
|
+
# graphql-ruby hands input objects over with Ruby-style keys, so the
|
|
8
|
+
# generated input's AND/OR/HAS fields arrive as :and/:or/:has.
|
|
9
|
+
LOGICAL_KEYS = { and: :AND, or: :OR, has: :HAS }.freeze
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def apply(relation, value)
|
|
13
|
+
return relation unless relation.respond_to?(:where)
|
|
14
|
+
|
|
15
|
+
condition = normalize_condition(value)
|
|
16
|
+
return relation unless condition
|
|
17
|
+
|
|
18
|
+
# AND narrows the incoming scope step by step; each OR branch starts
|
|
19
|
+
# from the incoming scope, and their union replaces it.
|
|
20
|
+
result = apply_all(relation, condition[:AND])
|
|
21
|
+
result = apply_any(relation, condition[:OR]) || result
|
|
22
|
+
result = apply_has_condition(result, condition[:HAS])
|
|
23
|
+
result = apply_single_condition(result, condition) if condition.key?(:column)
|
|
24
|
+
result
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def apply_has(relation, has_cond)
|
|
28
|
+
relation_name = has_cond[:relation].to_s
|
|
29
|
+
return relation if relation_name.blank?
|
|
30
|
+
|
|
31
|
+
resolved_relation_name = resolve_relation_name(relation, relation_name)
|
|
32
|
+
return relation if resolved_relation_name.blank?
|
|
33
|
+
|
|
34
|
+
reflection = reflection_for(relation, resolved_relation_name)
|
|
35
|
+
return relation unless reflection&.klass
|
|
36
|
+
|
|
37
|
+
joined = relation.joins(resolved_relation_name.to_sym)
|
|
38
|
+
return joined.distinct if has_cond[:condition].blank?
|
|
39
|
+
|
|
40
|
+
# Use an EXISTS-style filter to safely apply conditions from the related model.
|
|
41
|
+
child_scope = apply(reflection.klass.all, has_cond[:condition])
|
|
42
|
+
joined.where(reflection.klass.table_name => { id: child_scope.select(:id) }).distinct
|
|
43
|
+
rescue StandardError
|
|
44
|
+
relation
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def resolve_relation_name(relation, relation_name)
|
|
48
|
+
candidate_names = [relation_name.to_s, relation_name.to_s.singularize, relation_name.to_s.pluralize].uniq
|
|
49
|
+
|
|
50
|
+
candidate_names.each do |candidate|
|
|
51
|
+
next if candidate.blank?
|
|
52
|
+
return candidate if association_exists?(relation, candidate)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
nil
|
|
56
|
+
rescue StandardError
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def association_exists?(relation, name)
|
|
61
|
+
reflection_for(relation, name).present?
|
|
62
|
+
rescue StandardError
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def apply_has_argument(relation, arg_name, arg_value, relation_name = nil)
|
|
67
|
+
relation_name ||= derive_relation_name(arg_name)
|
|
68
|
+
return relation unless relation_name.present? && arg_value.present?
|
|
69
|
+
|
|
70
|
+
condition = arg_value.is_a?(Hash) ? arg_value : symbolized(arg_value)
|
|
71
|
+
has_payload = { relation: relation_name, condition: condition }
|
|
72
|
+
apply_has(relation, has_payload)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# The condition as a symbol-keyed Hash, or nil when there is none.
|
|
78
|
+
def normalize_condition(value)
|
|
79
|
+
return nil if value.nil?
|
|
80
|
+
|
|
81
|
+
condition = value.is_a?(Hash) ? value.deep_symbolize_keys : symbolized(value)
|
|
82
|
+
condition.transform_keys { |key| LOGICAL_KEYS.fetch(key, key) } if condition.is_a?(Hash)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def apply_all(relation, conditions)
|
|
86
|
+
Array(conditions).reduce(relation) { |acc, sub| apply(acc, sub) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# The union of the branches, or nil when there are none.
|
|
90
|
+
def apply_any(relation, conditions)
|
|
91
|
+
Array(conditions).map { |sub| apply(relation, sub) }.reduce { |acc, sub_scope| acc.or(sub_scope) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def apply_has_condition(relation, has_cond)
|
|
95
|
+
return relation unless has_cond.is_a?(Hash) && has_cond[:relation]
|
|
96
|
+
|
|
97
|
+
apply_has(relation, has_cond)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def reflection_for(relation, name)
|
|
101
|
+
klass = relation.respond_to?(:klass) ? relation.klass : relation
|
|
102
|
+
klass.reflect_on_association(name.to_sym) if klass.respond_to?(:reflect_on_association)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def derive_relation_name(arg_name)
|
|
106
|
+
Lighthouse::Support::Naming.derive_relation_name(arg_name)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def apply_single_condition(relation, condition)
|
|
110
|
+
return relation if condition[:column].blank? || !relation.respond_to?(:where)
|
|
111
|
+
|
|
112
|
+
column_name = resolve_column_name(condition[:column])
|
|
113
|
+
return relation if column_name.blank?
|
|
114
|
+
|
|
115
|
+
apply_operator(relation, column_name, operator_of(condition), condition[:value])
|
|
116
|
+
rescue StandardError
|
|
117
|
+
relation
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def operator_of(condition)
|
|
121
|
+
condition[:operator]&.to_s&.upcase || 'EQ'
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Null and set operators are plain hash conditions; the rest need Arel.
|
|
125
|
+
def apply_operator(relation, column_name, operator, value)
|
|
126
|
+
case operator
|
|
127
|
+
when 'IS_NULL' then relation.where(column_name => nil)
|
|
128
|
+
when 'IS_NOT_NULL' then relation.where.not(column_name => nil)
|
|
129
|
+
when 'IN' then relation.where(column_name => Array(value))
|
|
130
|
+
when 'NIN' then relation.where.not(column_name => Array(value))
|
|
131
|
+
else apply_arel_operator(relation, column_name, operator, value)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# An unknown operator falls back to equality rather than failing the query.
|
|
136
|
+
def apply_arel_operator(relation, column_name, operator, value)
|
|
137
|
+
pattern = like_pattern(operator, value)
|
|
138
|
+
return relation.where(arel_table(relation)[column_name].matches(pattern)) if pattern
|
|
139
|
+
|
|
140
|
+
predicate = SUPPORTED_OPERATORS[operator]
|
|
141
|
+
return relation.where(column_name => value) unless predicate
|
|
142
|
+
|
|
143
|
+
relation.where(arel_table(relation)[column_name].public_send(predicate, value))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def like_pattern(operator, value)
|
|
147
|
+
case operator
|
|
148
|
+
when 'CONTAINS' then "%#{value}%"
|
|
149
|
+
when 'STARTS_WITH' then "#{value}%"
|
|
150
|
+
when 'ENDS_WITH' then "%#{value}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def resolve_column_name(column)
|
|
155
|
+
Lighthouse::Support::Naming.column(column)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def arel_table(relation)
|
|
159
|
+
relation.klass.arel_table
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def symbolized(obj)
|
|
163
|
+
if obj.respond_to?(:to_h)
|
|
164
|
+
obj.to_h.deep_symbolize_keys
|
|
165
|
+
else
|
|
166
|
+
{}
|
|
166
167
|
end
|
|
167
168
|
end
|
|
168
169
|
end
|
|
@@ -1,40 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# directive AST node, and composes a constraint onto an ActiveRecord
|
|
10
|
-
# relation given the client-supplied value for that argument.
|
|
11
|
-
class Base
|
|
12
|
-
include Lighthouse::Contracts::ArgBuilder
|
|
3
|
+
# Base class for argument-builder directives (@eq, @neq, @in, @notIn,
|
|
4
|
+
# @like, @where, @orderBy). Each receives the argument AST node and the
|
|
5
|
+
# directive AST node, and composes a constraint onto an ActiveRecord
|
|
6
|
+
# relation given the client-supplied value for that argument.
|
|
7
|
+
class Lighthouse::GraphQL::Directives::Arguments::Base
|
|
8
|
+
include Lighthouse::Contracts::ArgBuilder
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
def initialize(arg_node, directive_node)
|
|
11
|
+
@arg_node = arg_node
|
|
12
|
+
@directive_node = directive_node
|
|
13
|
+
end
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
# @return relation
|
|
16
|
+
def handle_builder(_relation, _value)
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
end
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
private
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
# Column to filter on: the directive's `key:` argument if present,
|
|
23
|
+
# otherwise the snake_case form of the GraphQL argument name.
|
|
24
|
+
def column
|
|
25
|
+
Lighthouse::Support::DirectiveArgs.literal(@directive_node, 'key') ||
|
|
26
|
+
Lighthouse::Support::Naming.attribute(@arg_node.name)
|
|
27
|
+
end
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
29
|
+
def arel_column(relation)
|
|
30
|
+
relation.klass.arel_table[column]
|
|
39
31
|
end
|
|
40
32
|
end
|
|
@@ -2,100 +2,120 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'base'
|
|
4
4
|
|
|
5
|
-
module Lighthouse
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
5
|
+
module Lighthouse::GraphQL::Directives::Arguments
|
|
6
|
+
# @eq — equality filter (the default). `field(status: String @eq)`
|
|
7
|
+
class Eq < Base
|
|
8
|
+
def self.directive_name = 'eq'
|
|
9
|
+
def self.definition = 'directive @eq(key: String) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION'
|
|
10
|
+
|
|
11
|
+
def handle_builder(relation, value)
|
|
12
|
+
return relation if value.nil?
|
|
13
|
+
|
|
14
|
+
relation.where(column => value)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @whereKey — filter by the model's primary key, whatever its column is
|
|
19
|
+
# named; a list matches any of its values. `deletePosts(ids: [ID!]! @whereKey)`
|
|
20
|
+
class WhereKey < Base
|
|
21
|
+
def self.directive_name = 'whereKey'
|
|
22
|
+
def self.definition = 'directive @whereKey on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION'
|
|
23
|
+
|
|
24
|
+
def handle_builder(relation, value)
|
|
25
|
+
return relation if value.nil?
|
|
26
|
+
|
|
27
|
+
relation.where(relation.klass.primary_key => value)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @neq — inequality filter.
|
|
32
|
+
class Neq < Base
|
|
33
|
+
def self.directive_name = 'neq'
|
|
34
|
+
def self.definition = 'directive @neq(key: String) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION'
|
|
35
|
+
|
|
36
|
+
def handle_builder(relation, value)
|
|
37
|
+
return relation if value.nil?
|
|
38
|
+
|
|
39
|
+
relation.where.not(column => value)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @in — value is one of a list.
|
|
44
|
+
class In < Base
|
|
45
|
+
def self.directive_name = 'in'
|
|
46
|
+
def self.definition = 'directive @in(key: String) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION'
|
|
47
|
+
|
|
48
|
+
def handle_builder(relation, value)
|
|
49
|
+
return relation if value.nil?
|
|
50
|
+
|
|
51
|
+
relation.where(column => Array(value))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @notIn — value is none of a list.
|
|
56
|
+
class NotIn < Base
|
|
57
|
+
def self.directive_name = 'notIn'
|
|
58
|
+
def self.definition = 'directive @notIn(key: String) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION'
|
|
59
|
+
|
|
60
|
+
def handle_builder(relation, value)
|
|
61
|
+
return relation if value.nil?
|
|
62
|
+
|
|
63
|
+
relation.where.not(column => Array(value))
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @like — SQL LIKE with the value wrapped in wildcards.
|
|
68
|
+
class Like < Base
|
|
69
|
+
def self.directive_name = 'like'
|
|
70
|
+
def self.definition = 'directive @like(key: String) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION'
|
|
71
|
+
|
|
72
|
+
def handle_builder(relation, value)
|
|
73
|
+
return relation if value.nil?
|
|
74
|
+
|
|
75
|
+
relation.where(arel_column(relation).matches("%#{value}%"))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @where — generic operator filter, sharing the SQLOperator semantics with
|
|
80
|
+
# the @whereConditions extension. `field(age: Int @where(operator: "GT"))`
|
|
81
|
+
class Where < Base
|
|
82
|
+
def self.directive_name = 'where'
|
|
83
|
+
def self.definition = 'directive @where(key: String, operator: String) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION'
|
|
84
|
+
|
|
85
|
+
# LIKE pattern of each wildcard operator, built from the client value.
|
|
86
|
+
LIKE_PATTERNS = {
|
|
87
|
+
'CONTAINS' => ->(value) { "%#{value}%" },
|
|
88
|
+
'STARTS_WITH' => ->(value) { "#{value}%" },
|
|
89
|
+
'ENDS_WITH' => ->(value) { "%#{value}" }
|
|
90
|
+
}.freeze
|
|
91
|
+
|
|
92
|
+
def handle_builder(relation, value)
|
|
93
|
+
return relation if value.nil?
|
|
94
|
+
|
|
95
|
+
operator = sql_operator
|
|
96
|
+
case operator
|
|
97
|
+
when 'IN' then relation.where(column => Array(value))
|
|
98
|
+
when 'NIN' then relation.where.not(column => Array(value))
|
|
99
|
+
when 'IS_NULL' then relation.where(column => nil)
|
|
100
|
+
when 'IS_NOT_NULL' then relation.where.not(column => nil)
|
|
101
|
+
when *LIKE_PATTERNS.keys then relation.where(arel_column(relation).matches(LIKE_PATTERNS[operator].call(value)))
|
|
102
|
+
else compare(relation, operator, value)
|
|
98
103
|
end
|
|
99
104
|
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def sql_operator
|
|
109
|
+
(Lighthouse::Support::DirectiveArgs.literal(@directive_node, 'operator') || 'EQ').to_s.upcase
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Comparison operators map onto Arel predicates; an unknown one falls
|
|
113
|
+
# back to equality.
|
|
114
|
+
def compare(relation, operator, value)
|
|
115
|
+
arel_method = Lighthouse::WhereConditions::OperatorMap[operator]
|
|
116
|
+
return relation.where(column => value) unless arel_method
|
|
117
|
+
|
|
118
|
+
relation.where(arel_column(relation).public_send(arel_method, value))
|
|
119
|
+
end
|
|
100
120
|
end
|
|
101
121
|
end
|