bharat_filter_engine 0.1.2 → 0.2.0pre
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 +10 -9
- data/README.md +1 -1
- data/Rakefile +5 -3
- data/lib/bharat_filter_engine/association_resolver.rb +4 -4
- data/lib/bharat_filter_engine/dynamic_filter_values.rb +28 -12
- data/lib/bharat_filter_engine/engine.rb +211 -85
- data/lib/bharat_filter_engine/errors.rb +1 -1
- data/lib/bharat_filter_engine/rule_normalizer.rb +88 -30
- data/lib/bharat_filter_engine/search_builder.rb +34 -16
- data/lib/bharat_filter_engine/version.rb +1 -1
- data/lib/bharat_filter_engine.rb +12 -12
- data/spec/association_resolver_spec.rb +60 -41
- data/spec/dynamic_filter_values_spec.rb +121 -29
- data/spec/engine_spec.rb +1074 -540
- data/spec/rule_normalizer_spec.rb +181 -32
- data/spec/search_builder_spec.rb +178 -43
- data/spec/spec_helper.rb +7 -12
- metadata +8 -5
|
@@ -1,52 +1,201 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
RSpec.describe BharatFilterEngine::RuleNormalizer do
|
|
6
|
+
it 'keeps modern configuration unchanged' do
|
|
7
|
+
rule = {
|
|
8
|
+
type: :single,
|
|
9
|
+
filter_type: :string,
|
|
10
|
+
dbcolumn: :stage
|
|
11
|
+
}
|
|
4
12
|
|
|
5
|
-
|
|
13
|
+
expect(
|
|
14
|
+
described_class.call(rule)
|
|
15
|
+
).to eq(rule)
|
|
16
|
+
end
|
|
6
17
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
it 'supports legacy configuration' do
|
|
19
|
+
result =
|
|
20
|
+
described_class.call(
|
|
21
|
+
type: :string,
|
|
10
22
|
dbcolumn: :stage
|
|
11
|
-
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
expect(
|
|
26
|
+
result[:type]
|
|
27
|
+
).to eq(:single)
|
|
28
|
+
|
|
29
|
+
expect(
|
|
30
|
+
result[:filter_type]
|
|
31
|
+
).to eq(:string)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'returns nil for nil rule' do
|
|
35
|
+
expect(
|
|
36
|
+
described_class.call(nil)
|
|
37
|
+
).to be_nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'symbolizes rule keys' do
|
|
41
|
+
result = described_class.call(
|
|
42
|
+
'type' => :single,
|
|
43
|
+
'filter_type' => :string,
|
|
44
|
+
'dbcolumn' => :stage
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
expect(result[:type]).to eq(:single)
|
|
48
|
+
expect(result[:filter_type]).to eq(:string)
|
|
49
|
+
expect(result[:dbcolumn]).to eq(:stage)
|
|
50
|
+
end
|
|
12
51
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
52
|
+
describe 'validation' do
|
|
53
|
+
it 'raises for an unknown filter_type' do
|
|
54
|
+
expect do
|
|
55
|
+
described_class.call(
|
|
56
|
+
type: :single,
|
|
57
|
+
filter_type: :not_a_real_type,
|
|
58
|
+
dbcolumn: :stage
|
|
59
|
+
)
|
|
60
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
16
61
|
end
|
|
17
62
|
|
|
18
|
-
it
|
|
63
|
+
it 'raises when a nested rule is missing association' do
|
|
64
|
+
expect do
|
|
65
|
+
described_class.call(
|
|
66
|
+
type: :nested,
|
|
67
|
+
filter_type: :string,
|
|
68
|
+
dbcolumn: :source
|
|
69
|
+
)
|
|
70
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
71
|
+
end
|
|
19
72
|
|
|
20
|
-
|
|
73
|
+
it 'raises when a search rule is missing dbcolumns' do
|
|
74
|
+
expect do
|
|
21
75
|
described_class.call(
|
|
22
|
-
|
|
23
|
-
|
|
76
|
+
type: :single,
|
|
77
|
+
filter_type: :search
|
|
24
78
|
)
|
|
79
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
80
|
+
end
|
|
25
81
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
82
|
+
it 'raises when a search rule has an empty dbcolumns array' do
|
|
83
|
+
expect do
|
|
84
|
+
described_class.call(
|
|
85
|
+
type: :single,
|
|
86
|
+
filter_type: :search,
|
|
87
|
+
dbcolumns: []
|
|
88
|
+
)
|
|
89
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
90
|
+
end
|
|
29
91
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
92
|
+
it 'raises when a non-search rule is missing dbcolumn' do
|
|
93
|
+
expect do
|
|
94
|
+
described_class.call(
|
|
95
|
+
type: :single,
|
|
96
|
+
filter_type: :string
|
|
97
|
+
)
|
|
98
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
33
99
|
end
|
|
34
100
|
|
|
35
|
-
it
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
101
|
+
it 'raises for an unknown range_type' do
|
|
102
|
+
expect do
|
|
103
|
+
described_class.call(
|
|
104
|
+
type: :single,
|
|
105
|
+
filter_type: :integer,
|
|
106
|
+
dbcolumn: :amount,
|
|
107
|
+
range_type: :nonsense
|
|
108
|
+
)
|
|
109
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
39
110
|
end
|
|
40
111
|
|
|
41
|
-
it
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
112
|
+
it 'raises when range_type is used on a non-numeric filter_type' do
|
|
113
|
+
expect do
|
|
114
|
+
described_class.call(
|
|
115
|
+
type: :single,
|
|
116
|
+
filter_type: :string,
|
|
117
|
+
dbcolumn: :stage,
|
|
118
|
+
range_type: :gte
|
|
46
119
|
)
|
|
120
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
121
|
+
end
|
|
47
122
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
123
|
+
it 'allows range_type: :between on integer/float filters' do
|
|
124
|
+
expect do
|
|
125
|
+
described_class.call(
|
|
126
|
+
type: :single,
|
|
127
|
+
filter_type: :integer,
|
|
128
|
+
dbcolumn: :amount,
|
|
129
|
+
range_type: :between
|
|
130
|
+
)
|
|
131
|
+
end.not_to raise_error
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'raises when negate is combined with range_type' do
|
|
135
|
+
expect do
|
|
136
|
+
described_class.call(
|
|
137
|
+
type: :single,
|
|
138
|
+
filter_type: :integer,
|
|
139
|
+
dbcolumn: :amount,
|
|
140
|
+
range_type: :gte,
|
|
141
|
+
negate: true
|
|
142
|
+
)
|
|
143
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'raises when negate is combined with a daterange filter' do
|
|
147
|
+
expect do
|
|
148
|
+
described_class.call(
|
|
149
|
+
type: :single,
|
|
150
|
+
filter_type: :daterange,
|
|
151
|
+
dbcolumn: :actual_sale_date,
|
|
152
|
+
negate: true
|
|
153
|
+
)
|
|
154
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it 'raises when negate is combined with a search filter' do
|
|
158
|
+
expect do
|
|
159
|
+
described_class.call(
|
|
160
|
+
type: :single,
|
|
161
|
+
filter_type: :search,
|
|
162
|
+
dbcolumns: [:stage],
|
|
163
|
+
negate: true
|
|
164
|
+
)
|
|
165
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'raises when negate is combined with a presence filter' do
|
|
169
|
+
expect do
|
|
170
|
+
described_class.call(
|
|
171
|
+
type: :single,
|
|
172
|
+
filter_type: :presence,
|
|
173
|
+
dbcolumn: :stage,
|
|
174
|
+
negate: true
|
|
175
|
+
)
|
|
176
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'allows negate on a plain string/array/boolean/exact-numeric filter' do
|
|
180
|
+
expect do
|
|
181
|
+
described_class.call(
|
|
182
|
+
type: :single,
|
|
183
|
+
filter_type: :string,
|
|
184
|
+
dbcolumn: :stage,
|
|
185
|
+
negate: true
|
|
186
|
+
)
|
|
187
|
+
end.not_to raise_error
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'raises when filter_type: :search is combined with type: :nested' do
|
|
191
|
+
expect do
|
|
192
|
+
described_class.call(
|
|
193
|
+
type: :nested,
|
|
194
|
+
filter_type: :search,
|
|
195
|
+
association: :lead,
|
|
196
|
+
dbcolumns: [:source]
|
|
197
|
+
)
|
|
198
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
51
199
|
end
|
|
52
|
-
end
|
|
200
|
+
end
|
|
201
|
+
end
|
data/spec/search_builder_spec.rb
CHANGED
|
@@ -1,79 +1,78 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require 'spec_helper'
|
|
4
4
|
|
|
5
|
+
RSpec.describe BharatFilterEngine::SearchBuilder do
|
|
5
6
|
let!(:client) do
|
|
6
7
|
Client.create!(
|
|
7
|
-
name:
|
|
8
|
-
email:
|
|
8
|
+
name: 'John Doe',
|
|
9
|
+
email: 'john@example.com'
|
|
9
10
|
)
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
let!(:lead) do
|
|
13
14
|
Lead.create!(
|
|
14
|
-
source:
|
|
15
|
+
source: 'google',
|
|
15
16
|
client: client
|
|
16
17
|
)
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
let!(:sale) do
|
|
20
21
|
Sale.create!(
|
|
21
|
-
stage:
|
|
22
|
+
stage: 'qualified',
|
|
22
23
|
lead: lead
|
|
23
24
|
)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
it
|
|
27
|
-
|
|
27
|
+
it 'supports simple search' do
|
|
28
28
|
result =
|
|
29
29
|
described_class.new(
|
|
30
30
|
scope: Sale.all,
|
|
31
|
-
allowed_columns: [
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
allowed_columns: %i[
|
|
32
|
+
stage
|
|
33
|
+
lead__source
|
|
34
34
|
]
|
|
35
|
-
).apply(
|
|
35
|
+
).apply('google')
|
|
36
36
|
|
|
37
37
|
expect(result).to contain_exactly(sale)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
it
|
|
40
|
+
it 'supports case insensitive search' do
|
|
41
41
|
result =
|
|
42
42
|
described_class.new(
|
|
43
43
|
scope: Sale.all,
|
|
44
44
|
allowed_columns: [:stage]
|
|
45
|
-
).apply(
|
|
45
|
+
).apply('QUAL')
|
|
46
46
|
|
|
47
47
|
expect(result).to contain_exactly(sale)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
it
|
|
51
|
-
|
|
50
|
+
it 'supports nested field search' do
|
|
52
51
|
result =
|
|
53
52
|
described_class.new(
|
|
54
53
|
scope: Sale.all,
|
|
55
54
|
allowed_columns: [
|
|
56
|
-
:
|
|
55
|
+
:lead__client__name
|
|
57
56
|
]
|
|
58
57
|
).apply(
|
|
59
|
-
|
|
58
|
+
'lead__client__name=John'
|
|
60
59
|
)
|
|
61
60
|
|
|
62
61
|
expect(result).to contain_exactly(sale)
|
|
63
62
|
end
|
|
64
63
|
|
|
65
|
-
it
|
|
64
|
+
it 'supports multi-level association search' do
|
|
66
65
|
organization = Organization.create!(
|
|
67
|
-
name:
|
|
66
|
+
name: 'Acme'
|
|
68
67
|
)
|
|
69
68
|
|
|
70
69
|
client_two = Client.create!(
|
|
71
|
-
name:
|
|
70
|
+
name: 'Jane',
|
|
72
71
|
organization: organization
|
|
73
72
|
)
|
|
74
73
|
|
|
75
74
|
lead_two = Lead.create!(
|
|
76
|
-
source:
|
|
75
|
+
source: 'referral',
|
|
77
76
|
client: client_two
|
|
78
77
|
)
|
|
79
78
|
|
|
@@ -85,56 +84,55 @@ RSpec.describe BharatFilterEngine::SearchBuilder do
|
|
|
85
84
|
described_class.new(
|
|
86
85
|
scope: Sale.all,
|
|
87
86
|
allowed_columns: [
|
|
88
|
-
:
|
|
87
|
+
:lead__client__organization__name
|
|
89
88
|
]
|
|
90
89
|
).apply(
|
|
91
|
-
|
|
90
|
+
'lead__client__organization__name=Acme'
|
|
92
91
|
)
|
|
93
92
|
|
|
94
93
|
expect(result).to contain_exactly(sale_two)
|
|
95
94
|
end
|
|
96
95
|
|
|
97
|
-
it
|
|
98
|
-
|
|
96
|
+
it 'supports OR search' do
|
|
99
97
|
result =
|
|
100
98
|
described_class.new(
|
|
101
99
|
scope: Sale.all,
|
|
102
|
-
allowed_columns: [
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
allowed_columns: %i[
|
|
101
|
+
stage
|
|
102
|
+
lead__source
|
|
105
103
|
]
|
|
106
104
|
).apply(
|
|
107
|
-
|
|
105
|
+
'stage=qualified|lead__source=google'
|
|
108
106
|
)
|
|
109
107
|
|
|
110
108
|
expect(result).to contain_exactly(sale)
|
|
111
109
|
end
|
|
112
110
|
|
|
113
|
-
it
|
|
111
|
+
it 'supports combined AND and OR conditions' do
|
|
114
112
|
sale_one = Sale.create!(
|
|
115
|
-
stage:
|
|
116
|
-
approval_status:
|
|
113
|
+
stage: 'qualified',
|
|
114
|
+
approval_status: 'approved'
|
|
117
115
|
)
|
|
118
116
|
|
|
119
117
|
sale_two = Sale.create!(
|
|
120
|
-
stage:
|
|
121
|
-
approval_status:
|
|
118
|
+
stage: 'qualified',
|
|
119
|
+
approval_status: 'pending'
|
|
122
120
|
)
|
|
123
121
|
|
|
124
122
|
Sale.create!(
|
|
125
|
-
stage:
|
|
126
|
-
approval_status:
|
|
123
|
+
stage: 'new',
|
|
124
|
+
approval_status: 'approved'
|
|
127
125
|
)
|
|
128
126
|
|
|
129
127
|
result =
|
|
130
128
|
described_class.new(
|
|
131
129
|
scope: Sale.all,
|
|
132
|
-
allowed_columns: [
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
allowed_columns: %i[
|
|
131
|
+
stage
|
|
132
|
+
approval_status
|
|
135
133
|
]
|
|
136
134
|
).apply(
|
|
137
|
-
|
|
135
|
+
'stage=qualified&approval_status=approved|approval_status=pending'
|
|
138
136
|
)
|
|
139
137
|
|
|
140
138
|
expect(result).to contain_exactly(
|
|
@@ -143,15 +141,152 @@ RSpec.describe BharatFilterEngine::SearchBuilder do
|
|
|
143
141
|
)
|
|
144
142
|
end
|
|
145
143
|
|
|
146
|
-
it
|
|
144
|
+
it 'returns no records when the searched field is not configured' do
|
|
145
|
+
result =
|
|
146
|
+
described_class.new(
|
|
147
|
+
scope: Sale.all,
|
|
148
|
+
allowed_columns: [:stage]
|
|
149
|
+
).apply(
|
|
150
|
+
'unknown=value'
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
expect(result).to be_empty
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it 'supports searching the id column as text' do
|
|
157
|
+
result =
|
|
158
|
+
described_class.new(
|
|
159
|
+
scope: Sale.all,
|
|
160
|
+
allowed_columns: [:id]
|
|
161
|
+
).apply(
|
|
162
|
+
"id=#{sale.id}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
expect(result).to contain_exactly(sale)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it "handles a value that itself contains an '=' sign" do
|
|
169
|
+
sale_with_url = Sale.create!(
|
|
170
|
+
stage: 'https://example.com?x=1'
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
result =
|
|
174
|
+
described_class.new(
|
|
175
|
+
scope: Sale.all,
|
|
176
|
+
allowed_columns: [:stage]
|
|
177
|
+
).apply(
|
|
178
|
+
'stage=https://example.com?x=1'
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
expect(result).to contain_exactly(sale_with_url)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'skips a whitespace-only expression value' do
|
|
147
185
|
result =
|
|
148
186
|
described_class.new(
|
|
149
187
|
scope: Sale.all,
|
|
150
188
|
allowed_columns: [:stage]
|
|
151
189
|
).apply(
|
|
152
|
-
|
|
190
|
+
'stage= '
|
|
153
191
|
)
|
|
154
192
|
|
|
193
|
+
# every expression in the (only) AND group is blank/skipped, so
|
|
194
|
+
# the group can never match anything
|
|
155
195
|
expect(result).to be_empty
|
|
156
196
|
end
|
|
197
|
+
|
|
198
|
+
it 'returns the scope unchanged when allowed_columns is empty' do
|
|
199
|
+
result =
|
|
200
|
+
described_class.new(
|
|
201
|
+
scope: Sale.all,
|
|
202
|
+
allowed_columns: []
|
|
203
|
+
).apply('anything')
|
|
204
|
+
|
|
205
|
+
expect(result).to contain_exactly(sale)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it 'raises when none of the allowed_columns can be resolved' do
|
|
209
|
+
expect do
|
|
210
|
+
described_class.new(
|
|
211
|
+
scope: Sale.all,
|
|
212
|
+
allowed_columns: %i[
|
|
213
|
+
totally_not_a_column
|
|
214
|
+
also__not__real
|
|
215
|
+
]
|
|
216
|
+
)
|
|
217
|
+
end.to raise_error(BharatFilterEngine::InvalidConfigurationError)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'does not raise when only some allowed_columns are invalid' do
|
|
221
|
+
expect do
|
|
222
|
+
described_class.new(
|
|
223
|
+
scope: Sale.all,
|
|
224
|
+
allowed_columns: %i[
|
|
225
|
+
stage
|
|
226
|
+
totally_not_a_column
|
|
227
|
+
]
|
|
228
|
+
)
|
|
229
|
+
end.not_to raise_error
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it 'reuses a single AssociationResolver across a whole search' do
|
|
233
|
+
allow(BharatFilterEngine::AssociationResolver)
|
|
234
|
+
.to receive(:new)
|
|
235
|
+
.and_call_original
|
|
236
|
+
|
|
237
|
+
described_class.new(
|
|
238
|
+
scope: Sale.all,
|
|
239
|
+
allowed_columns: %i[
|
|
240
|
+
lead__source
|
|
241
|
+
lead__client__name
|
|
242
|
+
]
|
|
243
|
+
).apply(
|
|
244
|
+
'lead__source=google&lead__client__name=John'
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
expect(BharatFilterEngine::AssociationResolver)
|
|
248
|
+
.to have_received(:new)
|
|
249
|
+
.once
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
describe 'LIKE wildcard escaping' do
|
|
253
|
+
it 'treats a literal underscore as a literal character, not a wildcard' do
|
|
254
|
+
match = Sale.create!(stage: 'sale_2026')
|
|
255
|
+
Sale.create!(stage: 'saleX2026')
|
|
256
|
+
|
|
257
|
+
result =
|
|
258
|
+
described_class.new(
|
|
259
|
+
scope: Sale.all,
|
|
260
|
+
allowed_columns: [:stage]
|
|
261
|
+
).apply('sale_2026')
|
|
262
|
+
|
|
263
|
+
expect(result).to contain_exactly(match)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it 'treats a literal percent sign as a literal character, not a wildcard' do
|
|
267
|
+
match = Sale.create!(stage: '50%off')
|
|
268
|
+
Sale.create!(stage: '50xoff')
|
|
269
|
+
|
|
270
|
+
result =
|
|
271
|
+
described_class.new(
|
|
272
|
+
scope: Sale.all,
|
|
273
|
+
allowed_columns: [:stage]
|
|
274
|
+
).apply('50%off')
|
|
275
|
+
|
|
276
|
+
expect(result).to contain_exactly(match)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
it 'escapes wildcards in field-based search too' do
|
|
280
|
+
match = Sale.create!(stage: 'sale_2026')
|
|
281
|
+
Sale.create!(stage: 'saleX2026')
|
|
282
|
+
|
|
283
|
+
result =
|
|
284
|
+
described_class.new(
|
|
285
|
+
scope: Sale.all,
|
|
286
|
+
allowed_columns: [:stage]
|
|
287
|
+
).apply('stage=sale_2026')
|
|
288
|
+
|
|
289
|
+
expect(result).to contain_exactly(match)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
157
292
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'active_record'
|
|
5
|
+
require 'database_cleaner/active_record'
|
|
6
|
+
require 'bharat_filter_engine'
|
|
7
|
+
require 'sqlite3'
|
|
8
8
|
|
|
9
9
|
ActiveRecord::Base.establish_connection(
|
|
10
|
-
adapter:
|
|
11
|
-
database:
|
|
10
|
+
adapter: 'sqlite3',
|
|
11
|
+
database: ':memory:'
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
ActiveRecord::Schema.define do
|
|
@@ -41,29 +41,24 @@ ActiveRecord::Schema.define do
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
|
|
45
44
|
class Organization < ActiveRecord::Base
|
|
46
45
|
has_many :clients
|
|
47
46
|
end
|
|
48
47
|
|
|
49
|
-
|
|
50
48
|
class Client < ActiveRecord::Base
|
|
51
49
|
belongs_to :organization
|
|
52
50
|
has_many :leads
|
|
53
51
|
end
|
|
54
52
|
|
|
55
|
-
|
|
56
53
|
class Lead < ActiveRecord::Base
|
|
57
54
|
belongs_to :client
|
|
58
55
|
has_many :sales
|
|
59
56
|
end
|
|
60
57
|
|
|
61
|
-
|
|
62
58
|
class Sale < ActiveRecord::Base
|
|
63
59
|
belongs_to :lead
|
|
64
60
|
end
|
|
65
61
|
|
|
66
|
-
|
|
67
62
|
RSpec.configure do |config|
|
|
68
63
|
config.before(:suite) do
|
|
69
64
|
DatabaseCleaner.clean_with(:truncation)
|
metadata
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bharat_filter_engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0pre
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jayesh Rathore
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: activemodel
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
@@ -25,7 +25,7 @@ dependencies:
|
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '6.1'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: activerecord
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
@@ -53,7 +53,9 @@ dependencies:
|
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '6.1'
|
|
55
55
|
description: Configurable Rails ActiveRecord filtering engine supporting string, array,
|
|
56
|
-
boolean, numeric, date-range,
|
|
56
|
+
boolean, numeric (with gte/lte/between ranges), date-range, presence, negate/exclude,
|
|
57
|
+
nested association, and AND/OR search filters, plus dynamic filter-value lookups
|
|
58
|
+
for dropdowns/autocomplete.
|
|
57
59
|
email:
|
|
58
60
|
- jayeshrathore7024@gmail.com
|
|
59
61
|
executables: []
|
|
@@ -83,6 +85,7 @@ homepage: https://github.com/jayesh70240/bharat-filter-engine
|
|
|
83
85
|
licenses:
|
|
84
86
|
- MIT
|
|
85
87
|
metadata:
|
|
88
|
+
homepage_uri: https://github.com/jayesh70240/bharat-filter-engine
|
|
86
89
|
source_code_uri: https://github.com/jayesh70240/bharat-filter-engine
|
|
87
90
|
changelog_uri: https://github.com/jayesh70240/bharat-filter-engine/blob/main/CHANGELOG.md
|
|
88
91
|
bug_tracker_uri: https://github.com/jayesh70240/bharat-filter-engine/issues
|