lighthouse-graphql 0.6.0 → 0.7.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 +107 -1
- data/README.md +15 -7
- data/docs/best-practices.md +6 -4
- data/docs/configuration.md +19 -5
- data/docs/custom-directives.md +2 -1
- data/docs/directives.md +65 -7
- data/docs/federation.md +1 -1
- data/docs/filtering-and-ordering.md +330 -30
- data/docs/generators.md +2 -2
- data/docs/getting-started.md +43 -10
- data/docs/motivation.md +3 -2
- data/docs/performance.md +4 -3
- data/docs/relationships.md +2 -1
- data/docs/relay.md +52 -3
- data/docs/resolvers.md +2 -2
- data/docs/schema-organisation.md +177 -0
- data/lib/generators/lighthouse/install/graphql_ruby_install.rb +14 -0
- data/lib/generators/lighthouse/install/install_generator.rb +31 -12
- data/lib/generators/lighthouse/install/templates/graphql.config.yml.tt +2 -1
- data/lib/generators/lighthouse/install/templates/initializer.rb.tt +11 -4
- data/lib/generators/lighthouse/install/templates/schema.graphql.tt +30 -0
- data/lib/lighthouse/ast_builder.rb +124 -0
- data/lib/lighthouse/config.rb +23 -4
- data/lib/lighthouse/contracts/arg_manipulator.rb +9 -5
- data/lib/lighthouse/contracts/field_builder.rb +13 -0
- data/lib/lighthouse/directive_resolvers/aggregate_resolver.rb +1 -2
- data/lib/lighthouse/directive_resolvers/all_resolver.rb +0 -2
- data/lib/lighthouse/directive_resolvers/association_resolvers.rb +9 -6
- data/lib/lighthouse/directive_resolvers/base.rb +6 -37
- data/lib/lighthouse/directive_resolvers/builder_applier.rb +21 -6
- data/lib/lighthouse/directive_resolvers/count_resolver.rb +0 -1
- data/lib/lighthouse/directive_resolvers/first_resolver.rb +0 -2
- data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +2 -3
- data/lib/lighthouse/directives/arguments/order_by.rb +190 -26
- data/lib/lighthouse/directives/arguments/where_conditions.rb +25 -0
- data/lib/lighthouse/directives/arguments/where_conditions_base.rb +84 -0
- data/lib/lighthouse/directives/arguments/where_has_conditions.rb +56 -0
- data/lib/lighthouse/directives/definitions.rb +0 -1
- data/lib/lighthouse/directives/generated_types.rb +103 -0
- data/lib/lighthouse/directives/registry.rb +39 -10
- data/lib/lighthouse/document_ast.rb +111 -0
- data/lib/lighthouse/graphql/version.rb +1 -1
- data/lib/lighthouse/ide_helper.rb +80 -23
- data/lib/lighthouse/namespaces.rb +1 -0
- data/lib/lighthouse/order_by/types.rb +137 -0
- data/lib/lighthouse/railtie.rb +9 -2
- data/lib/lighthouse/rb_lighthouse.rb +15 -5
- data/lib/lighthouse/resolve_info.rb +5 -5
- data/lib/lighthouse/schema_cache.rb +8 -4
- data/lib/lighthouse/schema_factory.rb +6 -9
- data/lib/lighthouse/schema_generator.rb +4 -15
- data/lib/lighthouse/schema_stitcher.rb +64 -0
- data/lib/lighthouse/support/naming.rb +0 -12
- data/lib/lighthouse/where_conditions/columns.rb +36 -0
- data/lib/lighthouse/where_conditions/handler.rb +152 -0
- data/lib/lighthouse/where_conditions/json_column.rb +99 -0
- data/lib/lighthouse/where_conditions/operator_map.rb +1 -4
- data/lib/lighthouse/where_conditions/types.rb +110 -0
- data/sig/lighthouse/directives.rbs +16 -3
- data/sig/lighthouse/graphql.rbs +48 -4
- data/sig/lighthouse.rbs +8 -2
- metadata +15 -4
- data/lib/lighthouse/directive_resolvers/where_conditions_applier.rb +0 -170
- data/lib/lighthouse/directives/where_conditions_directive.rb +0 -244
- data/lib/lighthouse/sdl_loader.rb +0 -22
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The types @whereConditions and @whereHasConditions generate, as PHP
|
|
4
|
+
# Lighthouse's WhereConditionsServiceProvider defines them. For an argument
|
|
5
|
+
# whose input is named N:
|
|
6
|
+
#
|
|
7
|
+
# N column, operator, value, AND, OR, HAS: the conditions
|
|
8
|
+
# NRelation HAS: a relation, how many of its records (operator, amount), and a condition on them
|
|
9
|
+
# NHasCondition the condition on the related records: as N, but on any column
|
|
10
|
+
#
|
|
11
|
+
# Without `columns:` / `columnsEnum:` the argument shares the unrestricted
|
|
12
|
+
# WhereConditions (and its WhereConditionsRelation / WhereConditionsHasCondition).
|
|
13
|
+
module Lighthouse::WhereConditions::Types
|
|
14
|
+
DEFAULT = 'WhereConditions'
|
|
15
|
+
SQL_OPERATOR = 'SQLOperator'
|
|
16
|
+
MIXED = 'Mixed'
|
|
17
|
+
|
|
18
|
+
# The SQLOperator enum: what a condition compares the column with.
|
|
19
|
+
OPERATORS = {
|
|
20
|
+
'EQ' => 'Equal operator (`=`)',
|
|
21
|
+
'NEQ' => 'Not equal operator (`!=`)',
|
|
22
|
+
'GT' => 'Greater than operator (`>`)',
|
|
23
|
+
'GTE' => 'Greater than or equal operator (`>=`)',
|
|
24
|
+
'LT' => 'Less than operator (`<`)',
|
|
25
|
+
'LTE' => 'Less than or equal operator (`<=`)',
|
|
26
|
+
'LIKE' => 'Simple pattern matching (`LIKE`)',
|
|
27
|
+
'NOT_LIKE' => 'Negation of simple pattern matching (`NOT LIKE`)',
|
|
28
|
+
'IN' => 'Whether a value is within a set of values (`IN`)',
|
|
29
|
+
'NOT_IN' => 'Whether a value is not within a set of values (`NOT IN`)',
|
|
30
|
+
'BETWEEN' => 'Whether a value is within a range of values (`BETWEEN`)',
|
|
31
|
+
'NOT_BETWEEN' => 'Whether a value is not within a range of values (`NOT BETWEEN`)',
|
|
32
|
+
'IS_NULL' => 'Whether a value is null (`IS NULL`)',
|
|
33
|
+
'IS_NOT_NULL' => 'Whether a value is not null (`IS NOT NULL`)'
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
DEFAULT_OPERATOR = 'EQ'
|
|
37
|
+
DEFAULT_HAS_OPERATOR = 'GTE'
|
|
38
|
+
DEFAULT_HAS_AMOUNT = 1
|
|
39
|
+
|
|
40
|
+
module_function
|
|
41
|
+
|
|
42
|
+
# Defines N, NHasCondition and NRelation, and the SQLOperator and Mixed they use.
|
|
43
|
+
def define(document_ast, name, column_type, path)
|
|
44
|
+
document_ast.define_type(SQL_OPERATOR) { sql_operator }
|
|
45
|
+
document_ast.define_type(MIXED, 'scalar Mixed')
|
|
46
|
+
document_ast.define_type(name) { conditions(name, "Dynamic WHERE conditions for #{path}.", column_type) }
|
|
47
|
+
document_ast.define_type("#{name}HasCondition") do
|
|
48
|
+
conditions("#{name}HasCondition", "Dynamic WHERE HAS conditions for #{path}.", 'String')
|
|
49
|
+
end
|
|
50
|
+
document_ast.define_type("#{name}Relation") { relation(name, "Dynamic HAS conditions for WHERE conditions for #{path}.") }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def define_default(document_ast)
|
|
54
|
+
define(document_ast, DEFAULT, 'String', 'any column')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def conditions(name, description, column_type)
|
|
58
|
+
<<~GRAPHQL
|
|
59
|
+
"#{description}"
|
|
60
|
+
input #{name} {
|
|
61
|
+
"The column that is used for the condition."
|
|
62
|
+
column: #{column_type}
|
|
63
|
+
|
|
64
|
+
"The operator that is used for the condition."
|
|
65
|
+
operator: #{SQL_OPERATOR} = #{DEFAULT_OPERATOR}
|
|
66
|
+
|
|
67
|
+
"The value that is used for the condition."
|
|
68
|
+
value: #{MIXED}
|
|
69
|
+
|
|
70
|
+
"A set of conditions that requires all conditions to match."
|
|
71
|
+
AND: [#{name}!]
|
|
72
|
+
|
|
73
|
+
"A set of conditions that requires at least one condition to match."
|
|
74
|
+
OR: [#{name}!]
|
|
75
|
+
|
|
76
|
+
"Check whether a relation exists. Extra conditions or a minimum amount can be applied."
|
|
77
|
+
HAS: #{name.delete_suffix('HasCondition')}Relation
|
|
78
|
+
}
|
|
79
|
+
GRAPHQL
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def relation(name, description)
|
|
83
|
+
<<~GRAPHQL
|
|
84
|
+
"#{description}"
|
|
85
|
+
input #{name}Relation {
|
|
86
|
+
"The relation that is checked."
|
|
87
|
+
relation: String!
|
|
88
|
+
|
|
89
|
+
"The comparison operator to test against the amount."
|
|
90
|
+
operator: #{SQL_OPERATOR} = #{DEFAULT_HAS_OPERATOR}
|
|
91
|
+
|
|
92
|
+
"The amount to test."
|
|
93
|
+
amount: Int = #{DEFAULT_HAS_AMOUNT}
|
|
94
|
+
|
|
95
|
+
"Additional condition logic."
|
|
96
|
+
condition: #{name}HasCondition
|
|
97
|
+
}
|
|
98
|
+
GRAPHQL
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def sql_operator
|
|
102
|
+
values = OPERATORS.map { |value, description| %( "#{description}"\n #{value}) }
|
|
103
|
+
<<~GRAPHQL
|
|
104
|
+
"The available SQL operators that are used to filter query results."
|
|
105
|
+
enum #{SQL_OPERATOR} {
|
|
106
|
+
#{values.join("\n\n")}
|
|
107
|
+
}
|
|
108
|
+
GRAPHQL
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -36,9 +36,17 @@ module Lighthouse
|
|
|
36
36
|
include Directive
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# Its instances, built with (arg_node, directive_node), answer
|
|
40
|
+
# `manipulate_arg_definition(document_ast, field_node, type_node)` with the argument node.
|
|
39
41
|
module ArgManipulator
|
|
40
42
|
include Directive
|
|
41
43
|
end
|
|
44
|
+
|
|
45
|
+
# Its instances, built with (field_node, directive_node), answer
|
|
46
|
+
# `handle_field_builder(relation)` with the constrained relation.
|
|
47
|
+
module FieldBuilder
|
|
48
|
+
include Directive
|
|
49
|
+
end
|
|
42
50
|
end
|
|
43
51
|
|
|
44
52
|
class DirectiveRegistry
|
|
@@ -67,6 +75,9 @@ module Lighthouse
|
|
|
67
75
|
def resolution_names: () -> Array[String]
|
|
68
76
|
def arg_builder_class: (String | Symbol name) -> Class?
|
|
69
77
|
def arg_builder_names: () -> Array[String]
|
|
78
|
+
def arg_manipulator_class: (String | Symbol name) -> Class?
|
|
79
|
+
def arg_manipulator_classes: () -> Array[Class]
|
|
80
|
+
def field_builder_class: (String | Symbol name) -> Class?
|
|
70
81
|
|
|
71
82
|
# { "name" => "directive @name(...) on ..." }
|
|
72
83
|
def directive_definitions: () -> Hash[String, String]
|
|
@@ -83,9 +94,11 @@ module Lighthouse
|
|
|
83
94
|
PROGRAMMATIC_TYPES_FILE: String
|
|
84
95
|
|
|
85
96
|
# Writes both files into `output_dir` and returns their paths.
|
|
86
|
-
def self?.write: (output_dir: path, ?
|
|
97
|
+
def self?.write: (output_dir: path, ?schema_path: path?, ?registry: DirectiveRegistry) -> Array[String]
|
|
98
|
+
# Config.ide_helper's rewrite, on boot and every reload.
|
|
99
|
+
def self?.refresh: (?output_dir: path) -> Array[String]
|
|
87
100
|
def self?.directives_sdl: (?String sdl, ?registry: DirectiveRegistry) -> String
|
|
88
|
-
def self?.programmatic_types_sdl: (String sdl, ?registry: DirectiveRegistry) -> String
|
|
101
|
+
def self?.programmatic_types_sdl: (String sdl, ?registry: DirectiveRegistry, ?directives: String) -> String
|
|
89
102
|
end
|
|
90
103
|
|
|
91
104
|
module GraphQL
|
|
@@ -107,7 +120,7 @@ module Lighthouse
|
|
|
107
120
|
# `ctx`: the query's context, which @builder on an argument receives.
|
|
108
121
|
def apply_where_filters: (relation relation, untyped args, ?context? ctx) -> relation
|
|
109
122
|
def apply_arg_builders: (relation relation, arguments args_h, ?context? ctx) -> relation
|
|
110
|
-
def
|
|
123
|
+
def apply_field_builders: (relation relation, ?arguments args_h, ?context? ctx) -> relation
|
|
111
124
|
def constantize_model: (String? name) -> Class?
|
|
112
125
|
def apply_scopes: (relation relation) -> relation
|
|
113
126
|
|
data/sig/lighthouse/graphql.rbs
CHANGED
|
@@ -9,16 +9,60 @@ module Lighthouse
|
|
|
9
9
|
type schema = untyped
|
|
10
10
|
|
|
11
11
|
module RbLightHouse
|
|
12
|
-
# The schema built from the SDL
|
|
12
|
+
# The schema built from the SDL `schema_path` (default: Config.schema_path) and its imports.
|
|
13
13
|
# `options` takes :base_types, :base_schema and the schema's own options.
|
|
14
|
-
def self.get_schema: (?
|
|
14
|
+
def self.get_schema: (?schema_path: path?, ?options: Hash[Symbol, untyped]) -> schema
|
|
15
15
|
def self.configure: () ?{ (singleton(Lighthouse::Config) config) -> void } -> void
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
module SchemaFactory
|
|
19
|
-
def self?.build_base_schema: (?
|
|
19
|
+
def self?.build_base_schema: (?schema_path: path?, ?sdl: String?, ?base_types: Hash[untyped, untyped],
|
|
20
20
|
?schema_options: Hash[Symbol, untyped], **untyped extra_options) -> schema
|
|
21
|
-
def self?.load_sdl: (path?
|
|
21
|
+
def self?.load_sdl: (?path? schema_path) -> String
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Reads the schema from its entrypoint, pasting in what it #imports.
|
|
25
|
+
class SchemaStitcher
|
|
26
|
+
IMPORT: String
|
|
27
|
+
|
|
28
|
+
attr_reader root_schema_path: String
|
|
29
|
+
|
|
30
|
+
def initialize: (path root_schema_path) -> void
|
|
31
|
+
def schema_string: () -> String
|
|
32
|
+
# Every file the schema is read from, the entrypoint first.
|
|
33
|
+
def files: () -> Array[String]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The schema document the manipulation phase works on: each type defined once.
|
|
37
|
+
class DocumentAST
|
|
38
|
+
KEYWORDS: Hash[Class, String]
|
|
39
|
+
EXTENSIONS: Array[Class]
|
|
40
|
+
|
|
41
|
+
attr_reader types: Hash[String, untyped]
|
|
42
|
+
attr_reader type_extensions: Hash[String, Array[untyped]]
|
|
43
|
+
attr_reader directives: Hash[String, untyped]
|
|
44
|
+
attr_reader schema_definitions: Array[untyped]
|
|
45
|
+
|
|
46
|
+
def self.from_source: (String sdl) -> DocumentAST
|
|
47
|
+
def self.from_document: (untyped document) -> DocumentAST
|
|
48
|
+
|
|
49
|
+
def initialize: () -> void
|
|
50
|
+
def add: (untyped definition) -> untyped
|
|
51
|
+
# Adds a type, or replaces the one of the same name.
|
|
52
|
+
def put_type: (untyped definition) -> self
|
|
53
|
+
# Adds the type parsed from the SDL (or the block's), unless one of the name exists.
|
|
54
|
+
def define_type: (String name, ?String? sdl) ?{ () -> String } -> untyped
|
|
55
|
+
def type?: (String name) -> bool
|
|
56
|
+
def type_names: () -> Array[String]
|
|
57
|
+
def to_document: () -> untyped
|
|
58
|
+
def to_sdl: () -> String
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The manipulation phase: extensions merged, then the type, field and argument manipulators.
|
|
62
|
+
module ASTBuilder
|
|
63
|
+
ROOT_TYPES: Array[String]
|
|
64
|
+
|
|
65
|
+
def self?.build: (String sdl, ?registry: DirectiveRegistry) -> String
|
|
22
66
|
end
|
|
23
67
|
|
|
24
68
|
# Apollo's Automatic Persisted Queries, and a manifest of trusted operations.
|
data/sig/lighthouse.rbs
CHANGED
|
@@ -92,8 +92,14 @@ module Lighthouse
|
|
|
92
92
|
def self.[]: (Symbol | String key) -> untyped
|
|
93
93
|
def self.reset!: () -> void
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
def self.
|
|
95
|
+
# The schema's entrypoint, which imports the other files (SchemaStitcher).
|
|
96
|
+
def self.schema_path: () -> path?
|
|
97
|
+
def self.schema_path=: (path? path) -> path?
|
|
98
|
+
def self.schema_directory: () -> String?
|
|
99
|
+
|
|
100
|
+
# Rewrite the IDE helper files on boot and every reload; nil: in development with a graphql.config.yml.
|
|
101
|
+
def self.ide_helper: () -> bool?
|
|
102
|
+
def self.ide_helper=: (bool? enabled) -> bool?
|
|
97
103
|
|
|
98
104
|
# { queries:, mutations:, subscriptions:, builders:, validators:, events: } => [namespace, ...].
|
|
99
105
|
# Assigning a partial hash overrides only the kinds it names.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lighthouse-graphql
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vaz Innovation
|
|
@@ -70,6 +70,7 @@ files:
|
|
|
70
70
|
- docs/relationships.md
|
|
71
71
|
- docs/relay.md
|
|
72
72
|
- docs/resolvers.md
|
|
73
|
+
- docs/schema-organisation.md
|
|
73
74
|
- docs/security.md
|
|
74
75
|
- docs/subscriptions.md
|
|
75
76
|
- docs/types.md
|
|
@@ -87,6 +88,7 @@ files:
|
|
|
87
88
|
- lib/generators/lighthouse/subscription_generator.rb
|
|
88
89
|
- lib/generators/lighthouse/validator_generator.rb
|
|
89
90
|
- lib/lighthouse-graphql.rb
|
|
91
|
+
- lib/lighthouse/ast_builder.rb
|
|
90
92
|
- lib/lighthouse/async_mutation_job.rb
|
|
91
93
|
- lib/lighthouse/broadcast_job.rb
|
|
92
94
|
- lib/lighthouse/broadcasting.rb
|
|
@@ -98,6 +100,7 @@ files:
|
|
|
98
100
|
- lib/lighthouse/contracts/arg_builder.rb
|
|
99
101
|
- lib/lighthouse/contracts/arg_manipulator.rb
|
|
100
102
|
- lib/lighthouse/contracts/directive.rb
|
|
103
|
+
- lib/lighthouse/contracts/field_builder.rb
|
|
101
104
|
- lib/lighthouse/contracts/field_manipulator.rb
|
|
102
105
|
- lib/lighthouse/contracts/field_middleware.rb
|
|
103
106
|
- lib/lighthouse/contracts/field_resolver.rb
|
|
@@ -143,7 +146,6 @@ files:
|
|
|
143
146
|
- lib/lighthouse/directive_resolvers/throttle_resolver.rb
|
|
144
147
|
- lib/lighthouse/directive_resolvers/type_method_resolver.rb
|
|
145
148
|
- lib/lighthouse/directive_resolvers/validator_resolver.rb
|
|
146
|
-
- lib/lighthouse/directive_resolvers/where_conditions_applier.rb
|
|
147
149
|
- lib/lighthouse/directive_resolvers/with_count_resolver.rb
|
|
148
150
|
- lib/lighthouse/directive_resolvers/with_resolver.rb
|
|
149
151
|
- lib/lighthouse/directives/arguments/base.rb
|
|
@@ -151,7 +153,11 @@ files:
|
|
|
151
153
|
- lib/lighthouse/directives/arguments/more_filters.rb
|
|
152
154
|
- lib/lighthouse/directives/arguments/order_by.rb
|
|
153
155
|
- lib/lighthouse/directives/arguments/soft_deletes.rb
|
|
156
|
+
- lib/lighthouse/directives/arguments/where_conditions.rb
|
|
157
|
+
- lib/lighthouse/directives/arguments/where_conditions_base.rb
|
|
158
|
+
- lib/lighthouse/directives/arguments/where_has_conditions.rb
|
|
154
159
|
- lib/lighthouse/directives/definitions.rb
|
|
160
|
+
- lib/lighthouse/directives/generated_types.rb
|
|
155
161
|
- lib/lighthouse/directives/model_directive.rb
|
|
156
162
|
- lib/lighthouse/directives/node_directive.rb
|
|
157
163
|
- lib/lighthouse/directives/pagination_info_types.rb
|
|
@@ -159,7 +165,7 @@ files:
|
|
|
159
165
|
- lib/lighthouse/directives/registry.rb
|
|
160
166
|
- lib/lighthouse/directives/soft_deletes_directive.rb
|
|
161
167
|
- lib/lighthouse/directives/visibility_directive.rb
|
|
162
|
-
- lib/lighthouse/
|
|
168
|
+
- lib/lighthouse/document_ast.rb
|
|
163
169
|
- lib/lighthouse/enum_directive.rb
|
|
164
170
|
- lib/lighthouse/errors.rb
|
|
165
171
|
- lib/lighthouse/events.rb
|
|
@@ -184,6 +190,7 @@ files:
|
|
|
184
190
|
- lib/lighthouse/nested_mutations/many_to_many_operations.rb
|
|
185
191
|
- lib/lighthouse/nested_mutations/partitioner.rb
|
|
186
192
|
- lib/lighthouse/node_registry.rb
|
|
193
|
+
- lib/lighthouse/order_by/types.rb
|
|
187
194
|
- lib/lighthouse/pagination.rb
|
|
188
195
|
- lib/lighthouse/persisted_queries.rb
|
|
189
196
|
- lib/lighthouse/persisted_queries/batch.rb
|
|
@@ -201,7 +208,7 @@ files:
|
|
|
201
208
|
- lib/lighthouse/schema_factory.rb
|
|
202
209
|
- lib/lighthouse/schema_generator.rb
|
|
203
210
|
- lib/lighthouse/schema_implementation.rb
|
|
204
|
-
- lib/lighthouse/
|
|
211
|
+
- lib/lighthouse/schema_stitcher.rb
|
|
205
212
|
- lib/lighthouse/subscription.rb
|
|
206
213
|
- lib/lighthouse/support/arguments.rb
|
|
207
214
|
- lib/lighthouse/support/authorization.rb
|
|
@@ -217,7 +224,11 @@ files:
|
|
|
217
224
|
- lib/lighthouse/type_resolution.rb
|
|
218
225
|
- lib/lighthouse/upload.rb
|
|
219
226
|
- lib/lighthouse/upload_store.rb
|
|
227
|
+
- lib/lighthouse/where_conditions/columns.rb
|
|
228
|
+
- lib/lighthouse/where_conditions/handler.rb
|
|
229
|
+
- lib/lighthouse/where_conditions/json_column.rb
|
|
220
230
|
- lib/lighthouse/where_conditions/operator_map.rb
|
|
231
|
+
- lib/lighthouse/where_conditions/types.rb
|
|
221
232
|
- sig/lighthouse.rbs
|
|
222
233
|
- sig/lighthouse/controller.rbs
|
|
223
234
|
- sig/lighthouse/directives.rbs
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
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
|
-
{}
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
end
|