Skip to content

Commit a1d011e

Browse files
authored
fix(filters): properly parse filters from request params (#747)
1 parent 7f57f25 commit a1d011e

File tree

3 files changed

+102
-31
lines changed

3 files changed

+102
-31
lines changed

.github/workflows/build.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ jobs:
6565
run: |
6666
bundle exec rake test
6767
bundle exec rake db:migrate && bundle exec rspec --color --format doc
68-
- name: Send coverage
69-
uses: paambaati/[email protected]
70-
env:
71-
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
68+
# - name: Send coverage
69+
# uses: paambaati/[email protected]
70+
# env:
71+
# CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
7272

7373
deploy:
7474
name: Release

app/services/forest_liana/resources_getter.rb

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def compute_includes
168168
@includes = (includes & @field_names_requested).concat(includes_for_smart_search)
169169
else
170170
@includes = associations_has_one
171-
# Avoid eager loading has_one associations pointing to a different database as ORM can't join cross databases
171+
# Avoid eager loading has_one associations pointing to a different database as ORM can't join cross databases
172172
.reject { |association| separate_database?(@resource, association) }
173173
.map(&:name)
174174
end
@@ -198,15 +198,29 @@ def field_names_requested
198198

199199
def extract_associations_from_filter
200200
associations = []
201-
@params[:filter]&.each do |field, _|
202-
if field.include?(':')
201+
202+
filters = @params[:filters]
203+
filters = JSON.parse(filters) if filters.is_a?(String)
204+
205+
conditions = []
206+
207+
if filters.is_a?(Hash) && filters.key?('conditions')
208+
conditions = filters['conditions']
209+
elsif filters.is_a?(Hash) && filters.key?('field')
210+
conditions = [filters]
211+
end
212+
213+
conditions.each do |condition|
214+
field = condition['field']
215+
if field&.include?(':')
203216
associations << field.split(':').first.to_sym
204217
@count_needs_includes = true
205218
end
206219
end
220+
207221
@count_needs_includes = true if @params[:search]
208222

209-
associations
223+
associations.uniq
210224
end
211225

212226
def prepare_query
@@ -352,8 +366,8 @@ def compute_select_fields
352366

353367
def get_one_association(name)
354368
ForestLiana::QueryHelper.get_one_associations(@resource)
355-
.select { |association| association.name == name.to_sym }
356-
.first
369+
.select { |association| association.name == name.to_sym }
370+
.first
357371
end
358372
end
359373
end

spec/services/forest_liana/resources_getter_spec.rb

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def clean_database
3838
clean_database
3939

4040
users = ['Michel', 'Robert', 'Vince', 'Sandro', 'Olesya', 'Romain', 'Valentin', 'Jason', 'Arnaud', 'Jeff', 'Steve', 'Marc', 'Xavier', 'Paul', 'Mickael', 'Mike', 'Maxime', 'Gertrude', 'Monique', 'Mia', 'Rachid', 'Edouard', 'Sacha', 'Caro', 'Amand', 'Nathan', 'Noémie', 'Robin', 'Gaelle', 'Isabelle']
41-
.map { |name| User.create(name: name) }
41+
.map { |name| User.create(name: name) }
4242

4343
islands = [
4444
{ :name => 'Skull', :updated_at => Time.now - 1.years },
@@ -309,18 +309,18 @@ def association_connection.current_database
309309
describe 'when filtering on an ambiguous field' do
310310
let(:resource) { Tree }
311311
let(:pageSize) { 5 }
312-
let(:fields) { { 'Tree' => 'id' } }
312+
let(:fields) { { 'Tree' => 'id,name', 'cutter' => 'id' } }
313313
let(:filters) { {
314314
aggregator: 'and',
315315
conditions: [{
316-
field: 'created_at',
317-
operator: 'after',
318-
value: "#{Time.now - 6.year}",
319-
}, {
320-
field: 'cutter:name',
321-
operator: 'equal',
322-
value: 'Michel'
323-
}]
316+
field: 'created_at',
317+
operator: 'after',
318+
value: "#{Time.now - 6.year}",
319+
}, {
320+
field: 'cutter:name',
321+
operator: 'equal',
322+
value: 'Michel'
323+
}]
324324
}.to_json }
325325

326326
it 'should get only the expected records' do
@@ -332,7 +332,6 @@ def association_connection.current_database
332332
expect(count).to eq 1
333333
expect(records.first.id).to eq 3
334334
expect(records.first.name).to eq 'Apple Tree'
335-
expect(records.first.cutter.name).to eq 'Michel'
336335
end
337336
end
338337

@@ -379,7 +378,7 @@ def association_connection.current_database
379378
describe 'when sorting on an ambiguous field name with a filter' do
380379
let(:resource) { Tree }
381380
let(:sort) { '-name' }
382-
let(:fields) { { 'Tree' => 'id' } }
381+
let(:fields) { { 'Tree' => 'id,name' } }
383382
let(:filters) { {
384383
field: 'cutter:name',
385384
operator: 'equal',
@@ -417,7 +416,7 @@ def association_connection.current_database
417416

418417
describe 'when filtering on an updated_at field of an associated collection' do
419418
let(:resource) { Tree }
420-
let(:fields) { { 'Tree' => 'id' } }
419+
let(:fields) { { 'Tree' => 'id,name' } }
421420
let(:filters) { {
422421
field: 'island:updated_at',
423422
operator: 'previous_year'
@@ -510,9 +509,9 @@ def association_connection.current_database
510509

511510
it 'should raise the right error' do
512511
expect { getter }.to raise_error(
513-
ForestLiana::Errors::NotImplementedMethodError,
514-
"method filter on smart field 'alter_coordinates' not found"
515-
)
512+
ForestLiana::Errors::NotImplementedMethodError,
513+
"method filter on smart field 'alter_coordinates' not found"
514+
)
516515
end
517516
end
518517

@@ -555,10 +554,10 @@ def association_connection.current_database
555554
let(:filters) { {
556555
aggregator: 'and',
557556
conditions: [{
558-
field: 'name',
559-
operator: 'contains',
560-
value: 'a',
561-
}]
557+
field: 'name',
558+
operator: 'contains',
559+
value: 'a',
560+
}]
562561
}.to_json }
563562

564563
it 'should get only the records matching the scope' do
@@ -592,6 +591,64 @@ def association_connection.current_database
592591
end
593592
end
594593

594+
describe '#extract_associations_from_filter' do
595+
let(:resource) { Tree }
596+
597+
before { init_scopes }
598+
599+
context 'with a single filter as JSON string' do
600+
let(:filters) {
601+
{
602+
field: 'island:updated_at',
603+
operator: 'equal',
604+
value: '2024-01-01'
605+
}.to_json
606+
}
607+
608+
it 'extracts the correct association' do
609+
expect(getter.send(:extract_associations_from_filter)).to eq [:island]
610+
end
611+
end
612+
613+
context 'with grouped conditions as JSON string' do
614+
let(:filters) {
615+
{
616+
aggregator: 'and',
617+
conditions: [
618+
{ field: 'island:updated_at', operator: 'equal', value: '2024-01-01' },
619+
{ field: 'owner:name', operator: 'equal', value: 'Michel' },
620+
{ field: 'id', operator: 'present', value: nil }
621+
]
622+
}.to_json
623+
}
624+
625+
it 'extracts all unique associations' do
626+
expect(getter.send(:extract_associations_from_filter)).to match_array [:island, :owner]
627+
end
628+
end
629+
630+
context 'when filters has no association field' do
631+
let(:filters) {
632+
{
633+
field: 'id',
634+
operator: 'equal',
635+
value: 1
636+
}.to_json
637+
}
638+
639+
it 'returns an empty array' do
640+
expect(getter.send(:extract_associations_from_filter)).to eq []
641+
end
642+
end
643+
644+
context 'when filters is nil' do
645+
let(:filters) { nil }
646+
647+
it 'returns an empty array' do
648+
expect(getter.send(:extract_associations_from_filter)).to eq []
649+
end
650+
end
651+
end
595652
end
596653
end
597-
end
654+
end

0 commit comments

Comments
 (0)