Skip to content

Commit 34f4110

Browse files
feat: add filters on related data (#532)
1 parent a83d76e commit 34f4110

File tree

2 files changed

+266
-9
lines changed

2 files changed

+266
-9
lines changed

src/services/has-many-getter.js

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ const P = require('bluebird');
33
const Interface = require('forest-express');
44
const SearchBuilder = require('./search-builder');
55
const utils = require('../utils/schema');
6+
const FiltersParser = require('./filters-parser');
67

78
function HasManyGetter(model, association, opts, params) {
89
const OBJECTID_REGEXP = /^[0-9a-fA-F]{24}$/;
910
const schema = Interface.Schemas.schemas[utils.getModelName(association)];
1011
const searchBuilder = new SearchBuilder(association, opts, params);
12+
const filtersParser = new FiltersParser(association, params.timezone, opts);
1113

1214
function hasPagination() {
1315
return params.page && params.page.number;
@@ -45,6 +47,25 @@ function HasManyGetter(model, association, opts, params) {
4547
});
4648
}
4749

50+
async function buildConditions(recordIds) {
51+
const conditions = {
52+
$and: [{ _id: { $in: recordIds } }],
53+
};
54+
55+
if (params.search) {
56+
const conditionsSearch = await searchBuilder.getConditions();
57+
conditions.$and.push(conditionsSearch);
58+
}
59+
60+
if (params.filters) {
61+
const newFilters = await filtersParser.replaceAllReferences(params.filters);
62+
const newFiltersString = JSON.stringify(newFilters);
63+
conditions.$and.push(await filtersParser.perform(newFiltersString));
64+
}
65+
66+
return conditions;
67+
}
68+
4869
function getRecordsAndRecordIds() {
4970
return new P((resolve, reject) => {
5071
let id = params.recordId;
@@ -63,15 +84,7 @@ function HasManyGetter(model, association, opts, params) {
6384
});
6485
})
6586
.then(async (recordIds) => {
66-
const conditions = {
67-
$and: [{ _id: { $in: recordIds } }],
68-
};
69-
70-
if (params.search) {
71-
const conditionsSearch = await searchBuilder.getConditions();
72-
conditions.$and.push(conditionsSearch);
73-
}
74-
87+
const conditions = await buildConditions(recordIds);
7588
const query = association.find(conditions);
7689
handlePopulate(query);
7790

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
import mongoose from 'mongoose';
2+
import loadFixture from 'mongoose-fixture-loader';
3+
import Interface from 'forest-express';
4+
import mongooseConnect from '../../utils/mongoose-connect';
5+
import HasManyGetter from '../../../src/services/has-many-getter';
6+
7+
describe('service > has-many-getter', () => {
8+
let TreeModel;
9+
let LumberJackModel;
10+
11+
const options = {
12+
Mongoose: mongoose,
13+
connections: { mongoose },
14+
};
15+
16+
const parameters = {
17+
fields: {
18+
order: '_id,name,country',
19+
},
20+
recordId: '41224d776a326fb40f000003',
21+
associationName: 'owners',
22+
timezone: 'Europe/Paris',
23+
};
24+
25+
beforeAll(() => {
26+
Interface.Schemas = {
27+
schemas: {
28+
LumberJack: {
29+
name: 'LumberJack',
30+
idField: '_id',
31+
primaryKeys: ['_id'],
32+
isCompositePrimary: false,
33+
searchFields: ['name', 'country'],
34+
fields: [
35+
{
36+
field: '_id',
37+
type: 'ObjectId',
38+
},
39+
{
40+
field: 'name',
41+
type: 'String',
42+
},
43+
44+
{
45+
field: 'country',
46+
type: 'String',
47+
},
48+
],
49+
},
50+
Tree: {
51+
name: 'Tree',
52+
idField: '_id',
53+
primaryKeys: ['_id'],
54+
isCompositePrimary: false,
55+
searchFields: ['name'],
56+
fields: [
57+
{ field: '_id', type: 'ObjectId' },
58+
{ field: 'name', type: 'String' },
59+
{
60+
field: 'owners',
61+
type: ['ObjectId'],
62+
reference: 'LumberJack._id',
63+
},
64+
],
65+
},
66+
},
67+
};
68+
69+
return mongooseConnect()
70+
.then(() => {
71+
const LumberJackSchema = new mongoose.Schema({
72+
_id: { type: 'ObjectId' },
73+
name: { type: String },
74+
country: { type: String },
75+
});
76+
const TreeSchema = new mongoose.Schema({
77+
id: { type: 'ObjectId' },
78+
name: { type: String },
79+
owners: {
80+
type: ['ObjectId'],
81+
ref: 'LumberJack',
82+
},
83+
});
84+
85+
LumberJackModel = mongoose.model('LumberJack', LumberJackSchema);
86+
TreeModel = mongoose.model('Tree', TreeSchema);
87+
return Promise.all([LumberJackModel.remove({}), TreeModel.remove({})]);
88+
})
89+
.then(() => Promise.all([
90+
loadFixture(LumberJackModel, [
91+
{
92+
_id: '41224d776a326fb40f000001',
93+
name: 'Kaladin',
94+
country: 'CZ',
95+
},
96+
{
97+
_id: '41224d776a326fb40f000002',
98+
name: 'Marcell Doe',
99+
country: 'US',
100+
},
101+
{
102+
_id: '41224d776a326fb40f000004',
103+
name: 'Marc Schneider',
104+
country: 'DE',
105+
},
106+
{
107+
_id: '41224d776a326fb40f000005',
108+
name: 'Maria Smith',
109+
country: 'US',
110+
},
111+
]),
112+
loadFixture(TreeModel, [
113+
{
114+
_id: '41224d776a326fb40f000003',
115+
name: 'Ashe Tree Lane',
116+
owners: [
117+
'41224d776a326fb40f000001',
118+
'41224d776a326fb40f000002',
119+
'41224d776a326fb40f000004',
120+
'41224d776a326fb40f000005',
121+
],
122+
},
123+
]),
124+
]));
125+
});
126+
127+
afterAll(() => mongoose.connection.close());
128+
129+
it('should retrieve all the related records when there is no filters or search', async () => {
130+
expect.assertions(2);
131+
132+
const hasManyGetter = new HasManyGetter(
133+
TreeModel,
134+
LumberJackModel,
135+
options,
136+
parameters,
137+
);
138+
139+
const result = await hasManyGetter.perform();
140+
const count = await hasManyGetter.count();
141+
142+
143+
expect(result[0]).toHaveLength(4);
144+
expect(count).toBe(4);
145+
});
146+
147+
it('should retrieve matching records and count when search is specified', async () => {
148+
expect.assertions(3);
149+
150+
const hasManyGetter = new HasManyGetter(
151+
TreeModel,
152+
LumberJackModel,
153+
options,
154+
{
155+
...parameters,
156+
search: 'maria',
157+
},
158+
);
159+
160+
const result = await hasManyGetter.perform();
161+
const count = await hasManyGetter.count();
162+
163+
expect(result[0]).toHaveLength(1);
164+
expect(result[0][0].name).toBe('Maria Smith');
165+
expect(count).toBe(1);
166+
});
167+
168+
it('should retrieve matching records and count when filter is specified', async () => {
169+
expect.assertions(4);
170+
171+
const hasManyGetter = new HasManyGetter(
172+
TreeModel,
173+
LumberJackModel,
174+
options,
175+
{
176+
...parameters,
177+
filters: JSON.stringify({ field: 'name', operator: 'starts_with', value: 'Marc' }),
178+
},
179+
);
180+
181+
const result = await hasManyGetter.perform();
182+
const count = await hasManyGetter.count();
183+
184+
expect(result[0]).toHaveLength(2);
185+
expect(result[0][0].name).toBe('Marcell Doe');
186+
expect(result[0][1].name).toBe('Marc Schneider');
187+
expect(count).toBe(2);
188+
});
189+
190+
it('should retrieve matching records and count with filter with aggregator', async () => {
191+
expect.assertions(6);
192+
193+
const hasManyGetter = new HasManyGetter(
194+
TreeModel,
195+
LumberJackModel,
196+
options,
197+
{
198+
...parameters,
199+
filters: JSON.stringify({
200+
aggregator: 'and',
201+
conditions: [
202+
{ field: 'country', operator: 'equal', value: 'US' },
203+
{ field: 'name', operator: 'starts_with', value: 'Mar' },
204+
],
205+
}),
206+
},
207+
);
208+
209+
const result = await hasManyGetter.perform();
210+
const count = await hasManyGetter.count();
211+
212+
expect(result[0]).toHaveLength(2);
213+
expect(result[0][0].name).toBe('Marcell Doe');
214+
expect(result[0][1].name).toBe('Maria Smith');
215+
expect(result[0][0].country).toBe('US');
216+
expect(result[0][1].country).toBe('US');
217+
expect(count).toBe(2);
218+
});
219+
220+
it('should retrieve matching records and count when filter and search are specified', async () => {
221+
expect.assertions(6);
222+
223+
const hasManyGetter = new HasManyGetter(
224+
TreeModel,
225+
LumberJackModel,
226+
options,
227+
{
228+
...parameters,
229+
search: 'mar',
230+
filters: JSON.stringify({ field: 'country', operator: 'equal', value: 'US' }),
231+
},
232+
);
233+
234+
const result = await hasManyGetter.perform();
235+
const count = await hasManyGetter.count();
236+
237+
expect(result[0]).toHaveLength(2);
238+
expect(result[0][0].name).toBe('Marcell Doe');
239+
expect(result[0][1].name).toBe('Maria Smith');
240+
expect(result[0][0].country).toBe('US');
241+
expect(result[0][1].country).toBe('US');
242+
expect(count).toBe(2);
243+
});
244+
});

0 commit comments

Comments
 (0)