bharat_filter_engine 0.1.2 → 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/Gemfile +12 -9
- data/README.md +1 -1
- data/Rakefile +5 -3
- data/lib/bharat_filter_engine/association_resolver.rb +18 -37
- data/lib/bharat_filter_engine/column_helper.rb +28 -0
- data/lib/bharat_filter_engine/dynamic_filter_values.rb +17 -18
- data/lib/bharat_filter_engine/engine.rb +249 -91
- data/lib/bharat_filter_engine/errors.rb +1 -1
- data/lib/bharat_filter_engine/join_tracker.rb +37 -0
- data/lib/bharat_filter_engine/rule_normalizer.rb +88 -30
- data/lib/bharat_filter_engine/search_builder.rb +48 -24
- data/lib/bharat_filter_engine/version.rb +1 -1
- data/lib/bharat_filter_engine.rb +14 -12
- data/spec/association_resolver_spec.rb +60 -41
- data/spec/dynamic_filter_values_spec.rb +161 -29
- data/spec/engine_spec.rb +1144 -538
- data/spec/join_tracker_spec.rb +70 -0
- data/spec/rule_normalizer_spec.rb +181 -32
- data/spec/same_table_join_spec.rb +199 -0
- data/spec/search_builder_spec.rb +222 -42
- data/spec/spec_helper.rb +11 -12
- metadata +14 -6
|
@@ -2,46 +2,104 @@
|
|
|
2
2
|
|
|
3
3
|
module BharatFilterEngine
|
|
4
4
|
class RuleNormalizer
|
|
5
|
+
KNOWN_FILTER_TYPES = %i[
|
|
6
|
+
string
|
|
7
|
+
array
|
|
8
|
+
boolean
|
|
9
|
+
integer
|
|
10
|
+
float
|
|
11
|
+
daterange
|
|
12
|
+
search
|
|
13
|
+
presence
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
KNOWN_RANGE_TYPES = %i[gte lte between].freeze
|
|
17
|
+
NUMERIC_FILTER_TYPES = %i[integer float].freeze
|
|
18
|
+
NEGATE_UNSUPPORTED_FILTER_TYPES = %i[daterange search presence].freeze
|
|
19
|
+
|
|
5
20
|
def self.call(rule)
|
|
6
21
|
return nil unless rule
|
|
7
22
|
|
|
8
|
-
|
|
23
|
+
normalized = normalize(rule.transform_keys(&:to_sym))
|
|
24
|
+
validate!(normalized)
|
|
25
|
+
normalized
|
|
26
|
+
end
|
|
9
27
|
|
|
28
|
+
def self.normalize(rule)
|
|
10
29
|
return rule if rule[:filter_type].present?
|
|
11
30
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# Old:
|
|
15
|
-
# {
|
|
16
|
-
# type: :string,
|
|
17
|
-
# dbcolumn: :stage
|
|
18
|
-
# }
|
|
19
|
-
#
|
|
20
|
-
# New:
|
|
21
|
-
# {
|
|
22
|
-
# type: :single,
|
|
23
|
-
# filter_type: :string,
|
|
24
|
-
# dbcolumn: :stage
|
|
25
|
-
# }
|
|
26
|
-
|
|
27
|
-
scope_type =
|
|
28
|
-
if rule[:type] == :nested
|
|
29
|
-
:nested
|
|
30
|
-
else
|
|
31
|
-
:single
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
filter_type =
|
|
35
|
-
if rule[:type] == :nested
|
|
36
|
-
rule[:filter_type]
|
|
37
|
-
else
|
|
38
|
-
rule[:type]
|
|
39
|
-
end
|
|
31
|
+
scope_type = rule[:type] == :nested ? :nested : :single
|
|
32
|
+
filter_type = rule[:type] == :nested ? rule[:filter_type] : rule[:type]
|
|
40
33
|
|
|
41
34
|
rule.merge(
|
|
42
35
|
type: scope_type,
|
|
43
36
|
filter_type: filter_type
|
|
44
37
|
)
|
|
45
38
|
end
|
|
39
|
+
private_class_method :normalize
|
|
40
|
+
|
|
41
|
+
def self.validate!(rule)
|
|
42
|
+
filter_type = rule[:filter_type]
|
|
43
|
+
|
|
44
|
+
unless KNOWN_FILTER_TYPES.include?(filter_type)
|
|
45
|
+
raise InvalidConfigurationError,
|
|
46
|
+
"BharatFilterEngine: unknown filter_type #{filter_type.inspect}. " \
|
|
47
|
+
"Expected one of #{KNOWN_FILTER_TYPES.inspect}."
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if rule[:type] == :nested && rule[:association].blank?
|
|
51
|
+
raise InvalidConfigurationError,
|
|
52
|
+
'BharatFilterEngine: a `type: :nested` filter rule requires ' \
|
|
53
|
+
'an `association:` key.'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if rule[:type] == :nested && filter_type == :search
|
|
57
|
+
raise InvalidConfigurationError,
|
|
58
|
+
'BharatFilterEngine: `filter_type: :search` does not support ' \
|
|
59
|
+
'`type: :nested` -- use `__` notation inside `dbcolumns:` instead.'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if filter_type == :search
|
|
63
|
+
if Array(rule[:dbcolumns]).empty?
|
|
64
|
+
raise InvalidConfigurationError,
|
|
65
|
+
'BharatFilterEngine: a `filter_type: :search` rule requires ' \
|
|
66
|
+
'a non-empty `dbcolumns:` array.'
|
|
67
|
+
end
|
|
68
|
+
elsif rule[:dbcolumn].blank?
|
|
69
|
+
raise InvalidConfigurationError,
|
|
70
|
+
'BharatFilterEngine: filter rule requires a `dbcolumn:` key.'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
validate_range_type!(rule, filter_type)
|
|
74
|
+
validate_negate!(rule, filter_type)
|
|
75
|
+
end
|
|
76
|
+
private_class_method :validate!
|
|
77
|
+
|
|
78
|
+
def self.validate_range_type!(rule, filter_type)
|
|
79
|
+
return unless rule[:range_type]
|
|
80
|
+
|
|
81
|
+
unless KNOWN_RANGE_TYPES.include?(rule[:range_type])
|
|
82
|
+
raise InvalidConfigurationError,
|
|
83
|
+
"BharatFilterEngine: unknown range_type #{rule[:range_type].inspect}. " \
|
|
84
|
+
"Expected one of #{KNOWN_RANGE_TYPES.inspect}."
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
return if NUMERIC_FILTER_TYPES.include?(filter_type)
|
|
88
|
+
|
|
89
|
+
raise InvalidConfigurationError,
|
|
90
|
+
'BharatFilterEngine: `range_type:` is only supported for ' \
|
|
91
|
+
"#{NUMERIC_FILTER_TYPES.inspect} filters, got #{filter_type.inspect}."
|
|
92
|
+
end
|
|
93
|
+
private_class_method :validate_range_type!
|
|
94
|
+
|
|
95
|
+
def self.validate_negate!(rule, filter_type)
|
|
96
|
+
return unless rule[:negate]
|
|
97
|
+
return if rule[:range_type].blank? && !NEGATE_UNSUPPORTED_FILTER_TYPES.include?(filter_type)
|
|
98
|
+
|
|
99
|
+
raise InvalidConfigurationError,
|
|
100
|
+
'BharatFilterEngine: `negate: true` is not supported together ' \
|
|
101
|
+
"with `range_type:`, or with #{NEGATE_UNSUPPORTED_FILTER_TYPES.inspect} filters."
|
|
102
|
+
end
|
|
103
|
+
private_class_method :validate_negate!
|
|
46
104
|
end
|
|
47
|
-
end
|
|
105
|
+
end
|
|
@@ -2,10 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
module BharatFilterEngine
|
|
4
4
|
class SearchBuilder
|
|
5
|
-
|
|
5
|
+
LIKE_ESCAPE_CHAR = '\\'
|
|
6
|
+
|
|
7
|
+
def initialize(scope:, allowed_columns:, tracker: nil)
|
|
6
8
|
@scope = scope
|
|
9
|
+
@tracker = tracker || JoinTracker.new(scope.klass)
|
|
7
10
|
@allowed_columns =
|
|
8
11
|
Array(allowed_columns).map(&:to_s)
|
|
12
|
+
|
|
13
|
+
if @allowed_columns.present? &&
|
|
14
|
+
@allowed_columns.none? { |field| valid_field?(field) }
|
|
15
|
+
|
|
16
|
+
raise InvalidConfigurationError,
|
|
17
|
+
'BharatFilterEngine: none of the configured allowed_columns ' \
|
|
18
|
+
"#{@allowed_columns.inspect} could be resolved on " \
|
|
19
|
+
"#{@scope.klass}. Check `dbcolumns:` in your filter config."
|
|
20
|
+
end
|
|
9
21
|
end
|
|
10
22
|
|
|
11
23
|
def apply(value)
|
|
@@ -13,7 +25,7 @@ module BharatFilterEngine
|
|
|
13
25
|
|
|
14
26
|
search = value.to_s.strip
|
|
15
27
|
|
|
16
|
-
if search.include?(
|
|
28
|
+
if search.include?('=')
|
|
17
29
|
field_based_search(search)
|
|
18
30
|
else
|
|
19
31
|
simple_search(search)
|
|
@@ -23,13 +35,13 @@ module BharatFilterEngine
|
|
|
23
35
|
private
|
|
24
36
|
|
|
25
37
|
def simple_search(search)
|
|
26
|
-
query =
|
|
38
|
+
query = like_query(search)
|
|
27
39
|
conditions = []
|
|
28
40
|
|
|
29
41
|
@allowed_columns.each do |dbcolumn|
|
|
30
42
|
next unless valid_field?(dbcolumn)
|
|
31
43
|
|
|
32
|
-
if dbcolumn.include?(
|
|
44
|
+
if dbcolumn.include?('__')
|
|
33
45
|
@scope, condition =
|
|
34
46
|
nested_condition(
|
|
35
47
|
@scope,
|
|
@@ -41,7 +53,7 @@ module BharatFilterEngine
|
|
|
41
53
|
else
|
|
42
54
|
table = @scope.klass.arel_table
|
|
43
55
|
|
|
44
|
-
conditions << table[dbcolumn].matches(query)
|
|
56
|
+
conditions << table[dbcolumn].matches(query, LIKE_ESCAPE_CHAR)
|
|
45
57
|
end
|
|
46
58
|
end
|
|
47
59
|
|
|
@@ -60,19 +72,20 @@ module BharatFilterEngine
|
|
|
60
72
|
# is skipped and unrelated records leak through unfiltered.
|
|
61
73
|
def field_based_search(search)
|
|
62
74
|
and_conditions =
|
|
63
|
-
search.split(
|
|
75
|
+
search.split('&').map do |and_group|
|
|
64
76
|
or_conditions =
|
|
65
|
-
and_group.split(
|
|
77
|
+
and_group.split('|').filter_map do |expression|
|
|
66
78
|
field, search_value =
|
|
67
|
-
expression.split(
|
|
79
|
+
expression.split('=', 2)
|
|
68
80
|
|
|
69
81
|
next if field.blank?
|
|
70
82
|
next if search_value.blank?
|
|
83
|
+
next unless @allowed_columns.include?(field)
|
|
71
84
|
next unless valid_field?(field)
|
|
72
85
|
|
|
73
|
-
query =
|
|
86
|
+
query = like_query(search_value.strip)
|
|
74
87
|
|
|
75
|
-
if field.include?(
|
|
88
|
+
if field.include?('__')
|
|
76
89
|
@scope, condition =
|
|
77
90
|
nested_condition(
|
|
78
91
|
@scope,
|
|
@@ -81,17 +94,17 @@ module BharatFilterEngine
|
|
|
81
94
|
)
|
|
82
95
|
|
|
83
96
|
condition
|
|
84
|
-
elsif field ==
|
|
97
|
+
elsif field == 'id'
|
|
85
98
|
quoted_column =
|
|
86
99
|
@scope.klass
|
|
87
100
|
.connection
|
|
88
101
|
.quote_column_name(field)
|
|
89
102
|
|
|
90
103
|
Arel.sql(
|
|
91
|
-
"#{@scope.klass.quoted_table_name}.#{quoted_column}
|
|
92
|
-
).matches(query)
|
|
104
|
+
"CAST(#{@scope.klass.quoted_table_name}.#{quoted_column} AS TEXT)"
|
|
105
|
+
).matches(query, LIKE_ESCAPE_CHAR)
|
|
93
106
|
else
|
|
94
|
-
@scope.klass.arel_table[field].matches(query)
|
|
107
|
+
@scope.klass.arel_table[field].matches(query, LIKE_ESCAPE_CHAR)
|
|
95
108
|
end
|
|
96
109
|
end
|
|
97
110
|
|
|
@@ -110,7 +123,7 @@ module BharatFilterEngine
|
|
|
110
123
|
end
|
|
111
124
|
|
|
112
125
|
def valid_field?(field)
|
|
113
|
-
if field.include?(
|
|
126
|
+
if field.include?('__')
|
|
114
127
|
!resolver.resolve_column(field).nil?
|
|
115
128
|
else
|
|
116
129
|
@scope.klass.column_names.include?(field)
|
|
@@ -121,21 +134,32 @@ module BharatFilterEngine
|
|
|
121
134
|
result = resolver.resolve_column(field)
|
|
122
135
|
|
|
123
136
|
return [scope, nil] unless result
|
|
137
|
+
return [scope, subquery_condition(result, query)] if @tracker.claim(result) == :subquery
|
|
124
138
|
|
|
125
|
-
joined_scope =
|
|
126
|
-
resolver.join(
|
|
127
|
-
scope,
|
|
128
|
-
result[:associations]
|
|
129
|
-
)
|
|
139
|
+
joined_scope = resolver.join(scope, result[:associations]).distinct
|
|
130
140
|
|
|
131
|
-
condition =
|
|
132
|
-
result[:klass]
|
|
133
|
-
.arel_table[result[:column]]
|
|
134
|
-
.matches(query)
|
|
141
|
+
condition = result[:klass].arel_table[result[:column]].matches(query, LIKE_ESCAPE_CHAR)
|
|
135
142
|
|
|
136
143
|
[joined_scope, condition]
|
|
137
144
|
end
|
|
138
145
|
|
|
146
|
+
# `primary_key IN (SELECT ... JOIN ... WHERE column LIKE ...)` for a nested column whose
|
|
147
|
+
# table is already joined through another path (e.g. client + referrer -> clients).
|
|
148
|
+
def subquery_condition(result, query)
|
|
149
|
+
klass = @scope.klass
|
|
150
|
+
matcher = result[:klass].arel_table[result[:column]].matches(query, LIKE_ESCAPE_CHAR)
|
|
151
|
+
subquery = resolver.subquery(result[:associations]) { |relation| relation.where(matcher) }
|
|
152
|
+
|
|
153
|
+
klass.arel_table[klass.primary_key].in(subquery.arel)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def like_query(term)
|
|
157
|
+
escaped =
|
|
158
|
+
term.to_s.gsub(/[\\%_]/) { |char| "#{LIKE_ESCAPE_CHAR}#{char}" }
|
|
159
|
+
|
|
160
|
+
"%#{escaped}%"
|
|
161
|
+
end
|
|
162
|
+
|
|
139
163
|
# AssociationResolver only needs @scope.klass (which never changes
|
|
140
164
|
# across joins), so a single instance can be reused for the whole
|
|
141
165
|
# search instead of being rebuilt every time the scope is updated.
|
data/lib/bharat_filter_engine.rb
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
3
|
+
require 'active_record'
|
|
4
|
+
require 'active_model'
|
|
5
|
+
require 'active_support'
|
|
6
|
+
require 'json'
|
|
7
7
|
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
10
|
-
require_relative
|
|
11
|
-
require_relative
|
|
12
|
-
require_relative
|
|
13
|
-
require_relative
|
|
14
|
-
require_relative
|
|
8
|
+
require_relative 'bharat_filter_engine/version'
|
|
9
|
+
require_relative 'bharat_filter_engine/errors'
|
|
10
|
+
require_relative 'bharat_filter_engine/association_resolver'
|
|
11
|
+
require_relative 'bharat_filter_engine/column_helper'
|
|
12
|
+
require_relative 'bharat_filter_engine/rule_normalizer'
|
|
13
|
+
require_relative 'bharat_filter_engine/search_builder'
|
|
14
|
+
require_relative 'bharat_filter_engine/dynamic_filter_values'
|
|
15
|
+
require_relative 'bharat_filter_engine/join_tracker'
|
|
16
|
+
require_relative 'bharat_filter_engine/engine'
|
|
15
17
|
|
|
16
18
|
module BharatFilterEngine
|
|
17
19
|
class << self
|
|
@@ -31,4 +33,4 @@ module BharatFilterEngine
|
|
|
31
33
|
)
|
|
32
34
|
end
|
|
33
35
|
end
|
|
34
|
-
end
|
|
36
|
+
end
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'spec_helper'
|
|
4
4
|
|
|
5
5
|
RSpec.describe BharatFilterEngine::AssociationResolver do
|
|
6
|
-
|
|
7
6
|
let(:resolver) do
|
|
8
7
|
described_class.new(
|
|
9
8
|
Sale.all
|
|
10
9
|
)
|
|
11
10
|
end
|
|
12
11
|
|
|
13
|
-
describe
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
result = resolver.resolve("lead")
|
|
12
|
+
describe '#resolve' do
|
|
13
|
+
it 'resolves a direct association' do
|
|
14
|
+
result = resolver.resolve('lead')
|
|
17
15
|
|
|
18
16
|
expect(result[:associations]).to eq(
|
|
19
17
|
[:lead]
|
|
@@ -24,13 +22,13 @@ RSpec.describe BharatFilterEngine::AssociationResolver do
|
|
|
24
22
|
)
|
|
25
23
|
end
|
|
26
24
|
|
|
27
|
-
it
|
|
25
|
+
it 'resolves nested associations' do
|
|
28
26
|
result = resolver.resolve(
|
|
29
|
-
|
|
27
|
+
'lead__client'
|
|
30
28
|
)
|
|
31
29
|
|
|
32
30
|
expect(result[:associations]).to eq(
|
|
33
|
-
[
|
|
31
|
+
%i[lead client]
|
|
34
32
|
)
|
|
35
33
|
|
|
36
34
|
expect(result[:klass]).to eq(
|
|
@@ -38,16 +36,16 @@ RSpec.describe BharatFilterEngine::AssociationResolver do
|
|
|
38
36
|
)
|
|
39
37
|
end
|
|
40
38
|
|
|
41
|
-
it
|
|
39
|
+
it 'resolves multi-level associations' do
|
|
42
40
|
result = resolver.resolve(
|
|
43
|
-
|
|
41
|
+
'lead__client__organization'
|
|
44
42
|
)
|
|
45
43
|
|
|
46
44
|
expect(result[:associations]).to eq(
|
|
47
|
-
[
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
%i[
|
|
46
|
+
lead
|
|
47
|
+
client
|
|
48
|
+
organization
|
|
51
49
|
]
|
|
52
50
|
)
|
|
53
51
|
|
|
@@ -56,38 +54,51 @@ RSpec.describe BharatFilterEngine::AssociationResolver do
|
|
|
56
54
|
)
|
|
57
55
|
end
|
|
58
56
|
|
|
59
|
-
it
|
|
57
|
+
it 'returns nil for invalid association' do
|
|
60
58
|
result = resolver.resolve(
|
|
61
|
-
|
|
59
|
+
'unknown'
|
|
62
60
|
)
|
|
63
61
|
|
|
64
62
|
expect(result).to be_nil
|
|
65
63
|
end
|
|
66
|
-
end
|
|
67
64
|
|
|
68
|
-
|
|
65
|
+
it 'returns nil when an intermediate association in a multi-level path is invalid' do
|
|
66
|
+
result = resolver.resolve(
|
|
67
|
+
'lead__not_a_real_association__name'
|
|
68
|
+
)
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
expect(result).to be_nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'returns nil for a blank path' do
|
|
74
|
+
result = resolver.resolve('')
|
|
75
|
+
|
|
76
|
+
expect(result).to be_nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe '#resolve_column' do
|
|
81
|
+
it 'resolves direct columns' do
|
|
71
82
|
result =
|
|
72
83
|
resolver.resolve_column(
|
|
73
|
-
|
|
84
|
+
'stage'
|
|
74
85
|
)
|
|
75
86
|
|
|
76
87
|
expect(result[:column]).to eq(
|
|
77
|
-
|
|
88
|
+
'stage'
|
|
78
89
|
)
|
|
79
90
|
|
|
80
91
|
expect(result[:associations]).to eq([])
|
|
81
92
|
end
|
|
82
93
|
|
|
83
|
-
it
|
|
94
|
+
it 'resolves nested columns' do
|
|
84
95
|
result =
|
|
85
96
|
resolver.resolve_column(
|
|
86
|
-
|
|
97
|
+
'lead__source'
|
|
87
98
|
)
|
|
88
99
|
|
|
89
100
|
expect(result[:column]).to eq(
|
|
90
|
-
|
|
101
|
+
'source'
|
|
91
102
|
)
|
|
92
103
|
|
|
93
104
|
expect(result[:associations]).to eq(
|
|
@@ -95,18 +106,18 @@ RSpec.describe BharatFilterEngine::AssociationResolver do
|
|
|
95
106
|
)
|
|
96
107
|
end
|
|
97
108
|
|
|
98
|
-
it
|
|
109
|
+
it 'resolves multi-level columns' do
|
|
99
110
|
result =
|
|
100
111
|
resolver.resolve_column(
|
|
101
|
-
|
|
112
|
+
'lead__client__name'
|
|
102
113
|
)
|
|
103
114
|
|
|
104
115
|
expect(result[:column]).to eq(
|
|
105
|
-
|
|
116
|
+
'name'
|
|
106
117
|
)
|
|
107
118
|
|
|
108
119
|
expect(result[:associations]).to eq(
|
|
109
|
-
[
|
|
120
|
+
%i[lead client]
|
|
110
121
|
)
|
|
111
122
|
|
|
112
123
|
expect(result[:klass]).to eq(
|
|
@@ -114,19 +125,27 @@ RSpec.describe BharatFilterEngine::AssociationResolver do
|
|
|
114
125
|
)
|
|
115
126
|
end
|
|
116
127
|
|
|
117
|
-
it
|
|
128
|
+
it 'returns nil for invalid column' do
|
|
118
129
|
result =
|
|
119
130
|
resolver.resolve_column(
|
|
120
|
-
|
|
131
|
+
'lead__invalid'
|
|
121
132
|
)
|
|
122
133
|
|
|
123
134
|
expect(result).to be_nil
|
|
124
135
|
end
|
|
125
|
-
end
|
|
126
136
|
|
|
127
|
-
|
|
137
|
+
it 'returns nil when an intermediate association is invalid' do
|
|
138
|
+
result =
|
|
139
|
+
resolver.resolve_column(
|
|
140
|
+
'lead__not_a_real_association__name'
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
expect(result).to be_nil
|
|
144
|
+
end
|
|
145
|
+
end
|
|
128
146
|
|
|
129
|
-
|
|
147
|
+
describe '#join' do
|
|
148
|
+
it 'joins one association' do
|
|
130
149
|
scope =
|
|
131
150
|
resolver.join(
|
|
132
151
|
Sale.all,
|
|
@@ -136,25 +155,25 @@ RSpec.describe BharatFilterEngine::AssociationResolver do
|
|
|
136
155
|
expect(
|
|
137
156
|
scope.to_sql
|
|
138
157
|
).to include(
|
|
139
|
-
|
|
158
|
+
'JOIN'
|
|
140
159
|
)
|
|
141
160
|
end
|
|
142
161
|
|
|
143
|
-
it
|
|
162
|
+
it 'joins nested associations' do
|
|
144
163
|
scope =
|
|
145
164
|
resolver.join(
|
|
146
165
|
Sale.all,
|
|
147
|
-
[
|
|
148
|
-
|
|
149
|
-
|
|
166
|
+
%i[
|
|
167
|
+
lead
|
|
168
|
+
client
|
|
150
169
|
]
|
|
151
170
|
)
|
|
152
171
|
|
|
153
172
|
expect(
|
|
154
173
|
scope.to_sql
|
|
155
174
|
).to include(
|
|
156
|
-
|
|
175
|
+
'JOIN'
|
|
157
176
|
)
|
|
158
177
|
end
|
|
159
178
|
end
|
|
160
|
-
end
|
|
179
|
+
end
|