graphql-filters 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +1635 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +77 -0
- data/LICENSE +21 -0
- data/README.md +265 -0
- data/Rakefile +10 -0
- data/lib/graphql/filters/activerecord_patch/arel/nodes/contained.rb +9 -0
- data/lib/graphql/filters/activerecord_patch/arel/predications.rb +9 -0
- data/lib/graphql/filters/activerecord_patch.rb +3 -0
- data/lib/graphql/filters/dsl/graphql/schema/enum.rb +16 -0
- data/lib/graphql/filters/dsl/graphql/schema/field.rb +64 -0
- data/lib/graphql/filters/dsl/graphql/schema/list.rb +28 -0
- data/lib/graphql/filters/dsl/graphql/schema/member.rb +25 -0
- data/lib/graphql/filters/dsl/graphql/schema/non_null.rb +15 -0
- data/lib/graphql/filters/dsl/graphql/schema/object.rb +16 -0
- data/lib/graphql/filters/dsl/graphql/schema/scalar.rb +16 -0
- data/lib/graphql/filters/dsl/graphql/types/numeric.rb +20 -0
- data/lib/graphql/filters/dsl/graphql/types/string.rb +16 -0
- data/lib/graphql/filters/dsl.rb +3 -0
- data/lib/graphql/filters/filterable.rb +42 -0
- data/lib/graphql/filters/input_types/base_comparison_input_type.rb +12 -0
- data/lib/graphql/filters/input_types/base_list_comparison_input_type.rb +23 -0
- data/lib/graphql/filters/input_types/base_scalar_comparison_input_type.rb +63 -0
- data/lib/graphql/filters/input_types/fields_comparison_input_type.rb +58 -0
- data/lib/graphql/filters/input_types/list_object_comparison_input_type.rb +97 -0
- data/lib/graphql/filters/input_types/list_scalar_comparison_input_type.rb +134 -0
- data/lib/graphql/filters/input_types/numeric_comparison_input_type.rb +45 -0
- data/lib/graphql/filters/input_types/object_comparison_input_type.rb +71 -0
- data/lib/graphql/filters/input_types/string_comparison_input_type.rb +64 -0
- data/lib/graphql/filters/utility/cached_class.rb +74 -0
- data/lib/graphql/filters/version.rb +5 -0
- data/lib/graphql/filters.rb +26 -0
- data/lib/graphql/models_connect/dsl/graphql/schema/object.rb +27 -0
- data/lib/graphql/models_connect/dsl.rb +3 -0
- data/lib/graphql/models_connect.rb +15 -0
- metadata +126 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Filters
|
3
|
+
module Filterable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# We need the two inner_type chunks of code because type.list? is true if any of the types in the of_type chain
|
7
|
+
# is a list, but type.unwrap returns the raw type. I want the filter to be based on the of_type of the list,
|
8
|
+
# which could be the unwrapped type, but it could also be a non nullable version of that type. The non
|
9
|
+
# nullability is relevant.
|
10
|
+
|
11
|
+
included do
|
12
|
+
raise 'You can only apply a filter to a list field' unless type.list?
|
13
|
+
|
14
|
+
unless defined?(SearchObject::Base) && include?(SearchObject::Base)
|
15
|
+
raise 'If you don\'t use SearchObject, you must *prepend* Filterable, not *include* it.'
|
16
|
+
end
|
17
|
+
|
18
|
+
inner_type = type
|
19
|
+
inner_type = type.of_type until inner_type.kind == GraphQL::TypeKinds::LIST
|
20
|
+
inner_type = inner_type.of_type
|
21
|
+
|
22
|
+
option :filter, type: inner_type.comparison_input_type do |scope, filter|
|
23
|
+
filter.call scope
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
prepended do
|
28
|
+
raise 'You can only apply a filter to a list field' unless type.list?
|
29
|
+
|
30
|
+
inner_type = type
|
31
|
+
inner_type = type.of_type while inner_type.kind == GraphQL::TypeKinds::LIST
|
32
|
+
inner_type = inner_type.of_type
|
33
|
+
|
34
|
+
argument :filter, inner_type.comparison_input_type, required: false
|
35
|
+
|
36
|
+
def resolve filter: nil, **kwargs
|
37
|
+
filter.call super(**kwargs)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'base_comparison_input_type'
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Filters
|
5
|
+
module InputTypes
|
6
|
+
class BaseListComparisonInputType
|
7
|
+
include CachedClass
|
8
|
+
|
9
|
+
resolve_cache_miss do |value_type, klass|
|
10
|
+
klass.new BaseComparisonInputType do
|
11
|
+
graphql_name "#{value_type.unwrap.graphql_name}ListComparisonInput"
|
12
|
+
|
13
|
+
one_of
|
14
|
+
|
15
|
+
def prepare
|
16
|
+
values.sole
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative 'base_comparison_input_type'
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Filters
|
5
|
+
module InputTypes
|
6
|
+
class BaseScalarComparisonInputType
|
7
|
+
include CachedClass
|
8
|
+
|
9
|
+
resolve_cache_miss do |value_type, klass|
|
10
|
+
klass.new BaseComparisonInputType do
|
11
|
+
graphql_name "#{value_type.graphql_name}ComparisonInput"
|
12
|
+
|
13
|
+
one_of
|
14
|
+
|
15
|
+
argument :constant,
|
16
|
+
Types::Boolean,
|
17
|
+
prepare: lambda { |value, _context|
|
18
|
+
lambda { |scope, _column_name|
|
19
|
+
if value
|
20
|
+
scope.all
|
21
|
+
else
|
22
|
+
scope.where false
|
23
|
+
end
|
24
|
+
}
|
25
|
+
}
|
26
|
+
argument :equals,
|
27
|
+
value_type,
|
28
|
+
prepare: lambda { |value, _context|
|
29
|
+
lambda { |scope, column_name|
|
30
|
+
scope.where(column_name => value)
|
31
|
+
}
|
32
|
+
}
|
33
|
+
argument :not_equals,
|
34
|
+
value_type,
|
35
|
+
prepare: lambda { |value, _context|
|
36
|
+
lambda { |scope, column_name|
|
37
|
+
scope.where.not(column_name => value)
|
38
|
+
}
|
39
|
+
}
|
40
|
+
argument :in,
|
41
|
+
[value_type],
|
42
|
+
prepare: lambda { |value, _context|
|
43
|
+
lambda { |scope, column_name|
|
44
|
+
scope.where(column_name => value)
|
45
|
+
}
|
46
|
+
}
|
47
|
+
argument :not_in,
|
48
|
+
[value_type],
|
49
|
+
prepare: lambda { |value, _context|
|
50
|
+
lambda { |scope, column_name|
|
51
|
+
scope.where.not(column_name => value)
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
def prepare
|
56
|
+
values.sole
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative 'base_comparison_input_type'
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Filters
|
5
|
+
module InputTypes
|
6
|
+
class FieldsComparisonInputType
|
7
|
+
include CachedClass
|
8
|
+
|
9
|
+
resolve_cache_miss do |object_type, klass|
|
10
|
+
klass.new BaseComparisonInputType do
|
11
|
+
graphql_name "#{object_type.graphql_name}ComparisonInput"
|
12
|
+
|
13
|
+
object_type.fields.each_value do |field_object|
|
14
|
+
next unless field_object.filter_options[:enabled]
|
15
|
+
|
16
|
+
type = field_object.type
|
17
|
+
filter_options = field_object.filter_options
|
18
|
+
|
19
|
+
argument field_object.name,
|
20
|
+
type.comparison_input_type,
|
21
|
+
required: false,
|
22
|
+
prepare: lambda { |field_comparator, _context|
|
23
|
+
lambda { |scope|
|
24
|
+
if scope.klass.attribute_names.include? filter_options[:attribute_name]
|
25
|
+
field_comparator.call scope, filter_options[:attribute_name]
|
26
|
+
else
|
27
|
+
field_comparator.call scope, filter_options[:association_name]
|
28
|
+
end
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
define_method :prepare do
|
34
|
+
lambda do |scope, association_name=nil|
|
35
|
+
model_class = object_type.model_class
|
36
|
+
|
37
|
+
nested_query = values.reduce model_class.all do |acc, val|
|
38
|
+
val.call acc
|
39
|
+
end
|
40
|
+
|
41
|
+
if association_name.nil?
|
42
|
+
unless scope.structurally_compatible? nested_query
|
43
|
+
raise "the produced nested query on model #{model_class}"\
|
44
|
+
'is not compatible with the provided scope'
|
45
|
+
end
|
46
|
+
|
47
|
+
scope.and nested_query
|
48
|
+
else
|
49
|
+
scope.where association_name => nested_query
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require_relative 'base_list_comparison_input_type'
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Filters
|
5
|
+
module InputTypes
|
6
|
+
class ListObjectComparisonInputType
|
7
|
+
include CachedClass
|
8
|
+
|
9
|
+
resolve_cache_miss do |value_type, klass|
|
10
|
+
klass.new BaseListComparisonInputType[value_type] do
|
11
|
+
raw_type = value_type.unwrap
|
12
|
+
|
13
|
+
argument :constant,
|
14
|
+
Types::Boolean,
|
15
|
+
prepare: lambda { |value, _context|
|
16
|
+
lambda { |scope, _column_name=nil|
|
17
|
+
if value
|
18
|
+
scope.all
|
19
|
+
else
|
20
|
+
scope.where false
|
21
|
+
end
|
22
|
+
}
|
23
|
+
}
|
24
|
+
argument :any,
|
25
|
+
value_type.of_type.comparison_input_type,
|
26
|
+
prepare: lambda { |field_comparator, _context|
|
27
|
+
lambda { |scope, association_name|
|
28
|
+
subquery = field_comparator.call raw_type.model_class.all
|
29
|
+
|
30
|
+
reflection = scope.reflect_on_association association_name
|
31
|
+
|
32
|
+
finished_subquery = apply_reflection_to_subquery reflection, scope, subquery
|
33
|
+
|
34
|
+
scope.where finished_subquery.arel.exists
|
35
|
+
}
|
36
|
+
}
|
37
|
+
argument :all,
|
38
|
+
value_type.of_type.comparison_input_type,
|
39
|
+
prepare: lambda { |field_comparator, _context|
|
40
|
+
lambda { |scope, association_name|
|
41
|
+
subquery = field_comparator.call raw_type.model_class.all
|
42
|
+
|
43
|
+
reflection = scope.reflect_on_association association_name
|
44
|
+
|
45
|
+
finished_subquery = apply_reflection_to_subquery reflection, scope, subquery.invert_where
|
46
|
+
|
47
|
+
scope.where.not finished_subquery.arel.exists
|
48
|
+
}
|
49
|
+
}
|
50
|
+
argument :none,
|
51
|
+
value_type.of_type.comparison_input_type,
|
52
|
+
prepare: lambda { |field_comparator, _context|
|
53
|
+
lambda { |scope, association_name|
|
54
|
+
subquery = field_comparator.call raw_type.model_class.all
|
55
|
+
|
56
|
+
reflection = scope.reflect_on_association association_name
|
57
|
+
|
58
|
+
finished_subquery = apply_reflection_to_subquery reflection, scope, subquery
|
59
|
+
|
60
|
+
scope.where.not finished_subquery.arel.exists
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
class << self
|
65
|
+
private
|
66
|
+
|
67
|
+
def apply_reflection_to_subquery reflection, scope, subquery
|
68
|
+
unless reflection.is_a? ActiveRecord::Reflection::AbstractReflection
|
69
|
+
raise 'Lists of objects are only supported through associations at the moment'
|
70
|
+
end
|
71
|
+
|
72
|
+
scope_klass = scope.klass
|
73
|
+
scope_table = scope.table
|
74
|
+
scope_aliased_table = scope_table.alias "other_#{scope_table.name}"
|
75
|
+
|
76
|
+
table_metadata = ActiveRecord::TableMetadata.new scope_klass, scope_aliased_table
|
77
|
+
predicate_builder = ActiveRecord::PredicateBuilder.new table_metadata
|
78
|
+
aliased_relation = ActiveRecord::Relation.new scope_klass,
|
79
|
+
table: scope_aliased_table,
|
80
|
+
predicate_builder: predicate_builder
|
81
|
+
|
82
|
+
aliased_primary_key_node = scope_aliased_table[scope.primary_key]
|
83
|
+
scope_primary_key_node = scope_table[scope.primary_key]
|
84
|
+
|
85
|
+
aliased_relation
|
86
|
+
.joins(reflection.name)
|
87
|
+
.merge(subquery)
|
88
|
+
.where(aliased_primary_key_node.eq(scope_primary_key_node))
|
89
|
+
.select(:id) # Improves performance
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require_relative 'base_list_comparison_input_type'
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Filters
|
5
|
+
module InputTypes
|
6
|
+
class ListScalarComparisonInputType
|
7
|
+
include CachedClass
|
8
|
+
|
9
|
+
resolve_cache_miss do |value_type, klass|
|
10
|
+
klass.new BaseListComparisonInputType[value_type] do
|
11
|
+
argument :equals,
|
12
|
+
value_type,
|
13
|
+
prepare: lambda { |value, _context|
|
14
|
+
lambda { |scope, column_name|
|
15
|
+
scope.where(column_name => value)
|
16
|
+
}
|
17
|
+
}
|
18
|
+
argument :not_equals,
|
19
|
+
value_type,
|
20
|
+
prepare: lambda { |value, _context|
|
21
|
+
lambda { |scope, column_name|
|
22
|
+
scope.where.not(column_name => value)
|
23
|
+
}
|
24
|
+
}
|
25
|
+
argument :in,
|
26
|
+
[value_type],
|
27
|
+
prepare: lambda { |value, _context|
|
28
|
+
lambda { |scope, column_name|
|
29
|
+
scope.where(column_name => value)
|
30
|
+
}
|
31
|
+
}
|
32
|
+
argument :not_in,
|
33
|
+
[value_type],
|
34
|
+
prepare: lambda { |value, _context|
|
35
|
+
lambda { |scope, column_name|
|
36
|
+
scope.where.not(column_name => value)
|
37
|
+
}
|
38
|
+
}
|
39
|
+
argument :subset_of,
|
40
|
+
value_type,
|
41
|
+
prepare: lambda { |value, _context|
|
42
|
+
lambda { |scope, column_name|
|
43
|
+
table = scope.table[column_name]
|
44
|
+
attribute = scope.predicate_builder.build_bind_attribute column_name, value
|
45
|
+
scope.where table.contained(attribute)
|
46
|
+
}
|
47
|
+
}
|
48
|
+
argument :not_subset_of,
|
49
|
+
value_type,
|
50
|
+
prepare: lambda { |value, _context|
|
51
|
+
lambda { |scope, column_name|
|
52
|
+
table = scope.table[column_name]
|
53
|
+
attribute = scope.predicate_builder.build_bind_attribute column_name, value
|
54
|
+
scope.where.not table.contained(attribute)
|
55
|
+
}
|
56
|
+
}
|
57
|
+
argument :superset_of,
|
58
|
+
value_type,
|
59
|
+
prepare: lambda { |value, _context|
|
60
|
+
lambda { |scope, column_name|
|
61
|
+
table = scope.table[column_name]
|
62
|
+
attribute = scope.predicate_builder.build_bind_attribute column_name, value
|
63
|
+
scope.where table.contains(attribute)
|
64
|
+
}
|
65
|
+
}
|
66
|
+
argument :not_superset_of,
|
67
|
+
value_type,
|
68
|
+
prepare: lambda { |value, _context|
|
69
|
+
lambda { |scope, column_name|
|
70
|
+
table = scope.table[column_name]
|
71
|
+
attribute = scope.predicate_builder.build_bind_attribute column_name, value
|
72
|
+
scope.where.not table.contains(attribute)
|
73
|
+
}
|
74
|
+
}
|
75
|
+
argument :any,
|
76
|
+
value_type.of_type.comparison_input_type,
|
77
|
+
prepare: lambda { |field_comparator, _context|
|
78
|
+
lambda { |scope, column_name|
|
79
|
+
sub_scope = create_sub_scope scope, column_name, field_comparator
|
80
|
+
scope.where sub_scope.arel.exists
|
81
|
+
}
|
82
|
+
}
|
83
|
+
argument :all,
|
84
|
+
value_type.of_type.comparison_input_type,
|
85
|
+
prepare: lambda { |field_comparator, _context|
|
86
|
+
lambda { |scope, column_name|
|
87
|
+
sub_scope = create_sub_scope scope, column_name, field_comparator
|
88
|
+
scope.where.not sub_scope.invert_where.arel.exists
|
89
|
+
}
|
90
|
+
}
|
91
|
+
argument :none,
|
92
|
+
value_type.of_type.comparison_input_type,
|
93
|
+
prepare: lambda { |field_comparator, _context|
|
94
|
+
lambda { |scope, column_name|
|
95
|
+
sub_scope = create_sub_scope scope, column_name, field_comparator
|
96
|
+
scope.where.not sub_scope.arel.exists
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
class << self
|
101
|
+
private
|
102
|
+
|
103
|
+
def create_sub_scope scope, column_name, field_comparator
|
104
|
+
type_caster = Object.new
|
105
|
+
|
106
|
+
type_caster.define_singleton_method :type_for_attribute do |_name|
|
107
|
+
scope.klass.type_for_attribute(column_name).subtype
|
108
|
+
end
|
109
|
+
|
110
|
+
type_caster.define_singleton_method :type_cast_for_database do |attr_name, value|
|
111
|
+
type = type_for_attribute attr_name
|
112
|
+
type.serialize value
|
113
|
+
end
|
114
|
+
|
115
|
+
table = Arel::Table.new 'temp_table', type_caster: type_caster
|
116
|
+
predicate_builder = ActiveRecord::PredicateBuilder.new ActiveRecord::TableMetadata.new(
|
117
|
+
ActiveRecord::Base, table
|
118
|
+
)
|
119
|
+
temp_relation = ActiveRecord::Relation.new(ActiveRecord::Base,
|
120
|
+
table: table,
|
121
|
+
predicate_builder: predicate_builder)
|
122
|
+
.from(Arel::Nodes::NamedFunction.new 'UNNEST',
|
123
|
+
[scope.arel_table[column_name]],
|
124
|
+
'temp_table(value)')
|
125
|
+
|
126
|
+
field_comparator.call temp_relation, 'value'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative 'base_scalar_comparison_input_type'
|
2
|
+
require 'arel'
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
module Filters
|
6
|
+
module InputTypes
|
7
|
+
class NumericComparisonInputType
|
8
|
+
include CachedClass
|
9
|
+
|
10
|
+
resolve_cache_miss do |value_type, klass|
|
11
|
+
klass.new BaseScalarComparisonInputType[value_type] do
|
12
|
+
argument :greater_than,
|
13
|
+
value_type,
|
14
|
+
prepare: lambda { |value, _context|
|
15
|
+
lambda { |scope, column_name|
|
16
|
+
scope.where.not(column_name => (..value))
|
17
|
+
}
|
18
|
+
}
|
19
|
+
argument :greater_than_or_equals_to,
|
20
|
+
value_type,
|
21
|
+
prepare: lambda { |value, _context|
|
22
|
+
lambda { |scope, column_name|
|
23
|
+
scope.where(column_name => (value..))
|
24
|
+
}
|
25
|
+
}
|
26
|
+
argument :less_than,
|
27
|
+
value_type,
|
28
|
+
prepare: lambda { |value, _context|
|
29
|
+
lambda { |scope, column_name|
|
30
|
+
scope.where(column_name => (...value))
|
31
|
+
}
|
32
|
+
}
|
33
|
+
argument :less_than_or_equals_to,
|
34
|
+
value_type,
|
35
|
+
prepare: lambda { |value, _context|
|
36
|
+
lambda { |scope, column_name|
|
37
|
+
scope.where(column_name => (..value))
|
38
|
+
}
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative 'base_comparison_input_type'
|
2
|
+
require_relative 'fields_comparison_input_type'
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
module Filters
|
6
|
+
module InputTypes
|
7
|
+
class ObjectComparisonInputType
|
8
|
+
include CachedClass
|
9
|
+
|
10
|
+
resolve_cache_miss do |value_type, klass|
|
11
|
+
klass.new BaseComparisonInputType do
|
12
|
+
graphql_name "#{value_type.graphql_name}ComplexFilterInput"
|
13
|
+
|
14
|
+
fields_comparison_input_type = FieldsComparisonInputType[value_type]
|
15
|
+
|
16
|
+
define_singleton_method :fields_comparison_input_type do
|
17
|
+
fields_comparison_input_type
|
18
|
+
end
|
19
|
+
|
20
|
+
one_of
|
21
|
+
|
22
|
+
argument :constant,
|
23
|
+
Types::Boolean,
|
24
|
+
prepare: lambda { |value, _context|
|
25
|
+
lambda { |scope, _association_name=nil|
|
26
|
+
if value
|
27
|
+
scope.all
|
28
|
+
else
|
29
|
+
scope.where false
|
30
|
+
end
|
31
|
+
}
|
32
|
+
}
|
33
|
+
argument :and,
|
34
|
+
[self],
|
35
|
+
required: false,
|
36
|
+
prepare: lambda { |and_arg, _context|
|
37
|
+
lambda { |scope, association_name=nil|
|
38
|
+
and_arg.reduce scope do |acc, val|
|
39
|
+
acc.and val.call(scope, association_name)
|
40
|
+
end
|
41
|
+
}
|
42
|
+
}
|
43
|
+
argument :or,
|
44
|
+
[self],
|
45
|
+
required: false,
|
46
|
+
prepare: lambda { |or_arg, _context|
|
47
|
+
lambda { |scope, association_name=nil|
|
48
|
+
or_arg.reduce scope.none do |acc, val|
|
49
|
+
acc.or val.call(scope, association_name)
|
50
|
+
end
|
51
|
+
}
|
52
|
+
}
|
53
|
+
argument :not,
|
54
|
+
self,
|
55
|
+
required: false,
|
56
|
+
prepare: lambda { |not_arg, _context|
|
57
|
+
lambda { |scope, association_name=nil|
|
58
|
+
scope.and(not_arg.call(scope.unscope(:where), association_name).invert_where)
|
59
|
+
}
|
60
|
+
}
|
61
|
+
argument :fields, fields_comparison_input_type, required: false
|
62
|
+
|
63
|
+
def prepare
|
64
|
+
values.sole
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative 'base_scalar_comparison_input_type'
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Filters
|
5
|
+
module InputTypes
|
6
|
+
class StringComparisonInputType < BaseScalarComparisonInputType[GraphQL::Types::String]
|
7
|
+
argument :match,
|
8
|
+
String,
|
9
|
+
prepare: lambda { |value, _context|
|
10
|
+
lambda { |scope, column_name|
|
11
|
+
column_node = scope.table[column_name]
|
12
|
+
scope.where resolve_pattern(column_node, value)
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
class << self
|
17
|
+
alias inspect to_s
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def resolve_pattern column_node, expression
|
22
|
+
expression.match %r{v(?<version>\d)+/(?<full_pattern>.*)} do |match_data|
|
23
|
+
raise 'The only supported version of pattern is v1' if match_data[:version].to_i != 1
|
24
|
+
|
25
|
+
resolve_v1 column_node, match_data[:full_pattern]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def resolve_v1 column_node, full_pattern
|
30
|
+
full_pattern.match %r{(?<pattern>.*?)/(?<options>.*)} do |match_data|
|
31
|
+
options = match_data[:options].chars.map(&:to_sym)
|
32
|
+
|
33
|
+
if options.present? && options != [:i]
|
34
|
+
raise 'The only supported option is \'i\' for case insensitive matching'
|
35
|
+
end
|
36
|
+
|
37
|
+
case_sensitive = !options.include?(:i)
|
38
|
+
|
39
|
+
characters = match_data[:pattern].chars
|
40
|
+
pattern = ''
|
41
|
+
|
42
|
+
while characters.present?
|
43
|
+
char = characters.shift
|
44
|
+
|
45
|
+
pattern << case char
|
46
|
+
when '\\'
|
47
|
+
ActiveRecord::Base.sanitize_sql_like characters.shift
|
48
|
+
when '*'
|
49
|
+
'%'
|
50
|
+
when '.'
|
51
|
+
'_'
|
52
|
+
else
|
53
|
+
ActiveRecord::Base.sanitize_sql_like char
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
column_node.matches pattern, nil, case_sensitive
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
require 'active_support/core_ext/class/attribute'
|
4
|
+
|
5
|
+
module GraphQL
|
6
|
+
module Filters
|
7
|
+
module Utility
|
8
|
+
module CachedClass
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
next unless is_a? Class
|
13
|
+
|
14
|
+
class_attribute :cache,
|
15
|
+
default: Hash.new { |h, k|
|
16
|
+
h[k] = Class.new
|
17
|
+
},
|
18
|
+
instance_accessor: false,
|
19
|
+
instance_predicate: false
|
20
|
+
|
21
|
+
singleton_class.delegate :[], :[]=, to: :cache
|
22
|
+
singleton_class.undef_method :new
|
23
|
+
end
|
24
|
+
|
25
|
+
class CacheClassConstructor
|
26
|
+
attr_reader :key
|
27
|
+
attr_reader :owner
|
28
|
+
|
29
|
+
delegate_missing_to :owner
|
30
|
+
|
31
|
+
def initialize key, owner
|
32
|
+
@key = key
|
33
|
+
@owner = owner
|
34
|
+
end
|
35
|
+
|
36
|
+
def new base_class, &block
|
37
|
+
klass = Class.new base_class
|
38
|
+
owner[key] = klass
|
39
|
+
|
40
|
+
outer_self = self
|
41
|
+
|
42
|
+
klass.define_singleton_method :inspect do
|
43
|
+
"#{outer_self.owner}<#{outer_self.key}>"
|
44
|
+
end
|
45
|
+
|
46
|
+
klass.class_exec(&block)
|
47
|
+
klass
|
48
|
+
end
|
49
|
+
end
|
50
|
+
private_constant :CacheClassConstructor
|
51
|
+
|
52
|
+
class_methods do
|
53
|
+
# Pass this method the block that will be called on a cache miss. The block will receive the `key` that caused
|
54
|
+
# the miss, and a class constructor `klass`. Use `klass.new` instead of `Class.new` to create the class that
|
55
|
+
# you want to associate with `key` in the cache. This prevents loops in the case you reference the same key
|
56
|
+
# during the definition of the class. Notice that the class is first created with `Class.new`, then it is
|
57
|
+
# associated with `key` in the cache, and only then it is properly defined using `class_exec` and the block
|
58
|
+
# you passed to `new`, so be careful about what assumptions you make on the classes you pull from the cache
|
59
|
+
# during a cache miss resolution.
|
60
|
+
def resolve_cache_miss &block
|
61
|
+
cache.default_proc = proc do |h, k|
|
62
|
+
class_constructor = CacheClassConstructor.new k, self
|
63
|
+
result = block.call k, class_constructor
|
64
|
+
|
65
|
+
h[k] = result unless h.key? k
|
66
|
+
|
67
|
+
result
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|