Skip to content
This repository was archived by the owner on Sep 2, 2025. It is now read-only.

Commit 9649fea

Browse files
author
Dekel Barzilay
committed
Added $allowRefs query operator to allow referencing other fields
1 parent db178f9 commit 9649fea

File tree

3 files changed

+118
-13
lines changed

3 files changed

+118
-13
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,11 @@ Note that all this eager related options are optional.
178178

179179
- **`$mergeEager`** - merge an eager expression to `$eager`,
180180
e.g. `companies.find({ query: { $eager: 'employees', $mergeEager: 'ceos' } })`
181-
181+
182+
- **`$allowRefs`** - allow the usage of `ref` keyword to reference another field. Reference a relation's field using `$joinEager` or `$joinRelation`,
183+
e.g. `companies.find({ query: { name: 'ref(size)', $allowRefs: true } })`, `employees.find({ query: { $joinEager: 'company', 'company.name': 'ref(employees.name)', $allowRefs: true } })`. See
184+
[`ref`](https://vincit.github.io/objection.js/api/objection/#ref) documentation.
185+
182186
- **`$select`** - add SELECT statement with given array of column names, e.g. `['name', 'ref(jsonb:a)', 'ref(jsonb:a) as a']`. See
183187
[`select`](https://vincit.github.io/objection.js/api/query-builder/find-methods.html#select)
184188
and [`FieldExpression`](https://vincit.github.io/objection.js/api/types/#type-fieldexpression) documentation.

src/index.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,21 +154,23 @@ class Service extends AdapterService {
154154
* @param params
155155
* @param parentKey
156156
* @param methodKey
157+
* @param allowRefs
157158
*/
158-
objectify (query, params, parentKey, methodKey) {
159+
objectify (query, params, parentKey, methodKey, allowRefs) {
159160
if (params.$eager) { delete params.$eager; }
160161
if (params.$joinEager) { delete params.$joinEager; }
161162
if (params.$joinRelation) { delete params.$joinRelation; }
162163
if (params.$modifyEager) { delete params.$modifyEager; }
163164
if (params.$mergeEager) { delete params.$mergeEager; }
164165
if (params.$noSelect) { delete params.$noSelect; }
165166
if (params.$modify) { delete params.$modify; }
167+
if (params.$allowRefs) { delete params.$allowRefs; }
166168

167169
Object.keys(params || {}).forEach(key => {
168170
let value = params[key];
169171

170172
if (utils.isPlainObject(value)) {
171-
return this.objectify(query, value, key, parentKey);
173+
return this.objectify(query, value, key, parentKey, allowRefs);
172174
}
173175

174176
const column = parentKey && parentKey[0] !== '$' ? parentKey : key;
@@ -182,7 +184,7 @@ class Service extends AdapterService {
182184
return query.where(function () {
183185
return value.forEach((condition) => {
184186
this.orWhere(function () {
185-
self.objectify(this, condition);
187+
self.objectify(this, condition, null, null, allowRefs);
186188
});
187189
});
188190
});
@@ -194,7 +196,7 @@ class Service extends AdapterService {
194196
return query.where(function () {
195197
return value.forEach((condition) => {
196198
this.andWhere(function () {
197-
self.objectify(this, condition);
199+
self.objectify(this, condition, null, null, allowRefs);
198200
});
199201
});
200202
});
@@ -212,7 +214,7 @@ class Service extends AdapterService {
212214
if (columnType) {
213215
if (Array.isArray(columnType)) { columnType = columnType[0]; }
214216
if (columnType === 'object' || columnType === 'array') {
215-
let refColumn = null;
217+
let refColumn;
216218

217219
if (!methodKey && key[0] === '$') {
218220
refColumn = ref(`${this.Model.tableName}.${column}`);
@@ -242,6 +244,12 @@ class Service extends AdapterService {
242244
value = JSON.parse(value);
243245
}
244246

247+
if (allowRefs && typeof value === 'string') {
248+
const refMatches = value.match(/^ref\((.+)\)$/);
249+
250+
if (refMatches) { value = ref(refMatches[1]); }
251+
}
252+
245253
return operator === '=' ? query.where(column, value) : query.where(column, operator, value);
246254
});
247255
}
@@ -337,15 +345,15 @@ class Service extends AdapterService {
337345
const eagerFilterQuery = query.$modifyEager[eagerFilterExpression];
338346

339347
q.modifyGraph(eagerFilterExpression, builder => {
340-
this.objectify(builder, eagerFilterQuery);
348+
this.objectify(builder, eagerFilterQuery, null, null, query.$allowRefs);
341349
});
342350
}
343351

344352
delete query.$modifyEager;
345353
}
346354

347355
// build up the knex query out of the query params
348-
this.objectify(q, query);
356+
this.objectify(q, query, null, null, query.$allowRefs);
349357

350358
if (filters.$sort) {
351359
Object.keys(filters.$sort).forEach(item => {
@@ -414,7 +422,7 @@ class Service extends AdapterService {
414422
countQuery.count({ total: idColumns });
415423
}
416424

417-
this.objectify(countQuery, query);
425+
this.objectify(countQuery, query, null, null, query.$allowRefs);
418426

419427
return countQuery
420428
.then(count => parseInt(count[0].total, 10))
@@ -596,7 +604,7 @@ class Service extends AdapterService {
596604

597605
const q = this._createQuery(params);
598606

599-
this.objectify(q, query);
607+
this.objectify(q, query, null, null, query.$allowRefs);
600608

601609
if (Array.isArray(this.id)) {
602610
for (const idKey of this.id) {
@@ -661,7 +669,7 @@ class Service extends AdapterService {
661669
const { query: queryParams } = this.filterQuery(params);
662670
const query = this._createQuery(params);
663671

664-
this.objectify(query, queryParams);
672+
this.objectify(query, queryParams, null, null, query.$allowRefs);
665673

666674
if (params.query && params.query.$noSelect) {
667675
return query.delete().then(() => {

test/index.test.js

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ const app = feathers()
134134
model: Company,
135135
id: 'id',
136136
multi: ['create', 'remove', 'patch'],
137-
whitelist: ['$eager', '$joinRelation', '$modifyEager', '$mergeEager', '$between', '$notBetween', '$containsKey', '$contains', '$contained', '$any', '$all', '$noSelect', '$like', '$null', '$modify'],
137+
whitelist: ['$eager', '$joinRelation', '$modifyEager', '$mergeEager', '$between', '$notBetween', '$containsKey', '$contains', '$contained', '$any', '$all', '$noSelect', '$like', '$null', '$modify', '$allowRefs'],
138138
allowedEager: '[ceos, clients, employees]',
139139
eagerFilters: [
140140
{
@@ -154,7 +154,7 @@ const app = feathers()
154154
service({
155155
model: Employee,
156156
multi: ['create'],
157-
whitelist: ['$eager', '$joinRelation', '$joinEager', '$like'],
157+
whitelist: ['$eager', '$joinRelation', '$joinEager', '$like', '$allowRefs'],
158158
allowedEager: 'company',
159159
eagerFilters: {
160160
expression: 'ltd',
@@ -1823,4 +1823,97 @@ describe('Feathers Objection Service', () => {
18231823
});
18241824
});
18251825
});
1826+
1827+
describe('$allowRefs', () => {
1828+
const ids = {};
1829+
1830+
beforeEach(async () => {
1831+
ids.ceo = await people
1832+
.create({
1833+
name: 'Snoop',
1834+
age: 20
1835+
});
1836+
1837+
ids.companies = await companies
1838+
.create([
1839+
{
1840+
name: 'small',
1841+
ceo: ids.ceo.id,
1842+
size: 'small'
1843+
},
1844+
{
1845+
name: 'Apple',
1846+
ceo: ids.ceo.id,
1847+
size: 'large'
1848+
}
1849+
]);
1850+
1851+
const [small, apple] = ids.companies;
1852+
1853+
ids.employees = await employees
1854+
.create([
1855+
{
1856+
name: 'John',
1857+
companyId: small.id
1858+
},
1859+
{
1860+
name: 'Apple',
1861+
companyId: apple.id
1862+
}
1863+
]);
1864+
});
1865+
1866+
afterEach(async () => {
1867+
await people.remove(ids.ceo.id);
1868+
1869+
for (const employee of ids.employees) { await employees.remove(employee.id); }
1870+
1871+
for (const company of ids.companies) { await companies.remove(company.id); }
1872+
});
1873+
1874+
it('allow allowRefs queries', () => {
1875+
return companies.find({ query: { name: 'ref(size)', $allowRefs: true } }).then(data => {
1876+
expect(data.length).to.equal(1);
1877+
expect(data[0].name).to.equal('small');
1878+
});
1879+
});
1880+
1881+
it('query with ref when not allowed', () => {
1882+
return companies.find({ query: { name: 'ref(size)' } }).then(data => {
1883+
expect(data.length).to.equal(0);
1884+
});
1885+
});
1886+
1887+
it('patch with ref', () => {
1888+
return companies.patch(null, { ceo: null }, { query: { name: 'ref(size)', $allowRefs: true } }).then(data => {
1889+
expect(data.length).to.equal(1);
1890+
expect(data[0].ceo).to.be.null;
1891+
});
1892+
});
1893+
1894+
it('remove with ref', () => {
1895+
return companies.remove(null, { query: { name: 'ref(size)', $allowRefs: true } }).then(data => {
1896+
expect(data.length).to.equal(1);
1897+
expect(data[0].name).to.equal('small');
1898+
});
1899+
});
1900+
1901+
it('joinEager queries with ref', () => {
1902+
return employees.find({ query: { $joinEager: 'company', 'company.name': 'ref(employees.name)', $allowRefs: true } }).then(data => {
1903+
expect(data.length).to.equal(1);
1904+
expect(data[0].name).to.equal('Apple');
1905+
expect(data[0].company).to.be.ok;
1906+
expect(data[0].company.name).to.equal('Apple');
1907+
});
1908+
});
1909+
1910+
it('joinRelation queries with ref', () => {
1911+
return employees.find({ query: { $eager: 'company', $joinRelation: 'company', 'company.name': 'ref(employees.name)', $allowRefs: true } }).then(data => {
1912+
expect(data.length).to.equal(1);
1913+
expect(data[0].name).to.equal('Apple');
1914+
expect(data[0].company).to.be.ok;
1915+
expect(data[0].company.name).to.equal('Apple');
1916+
});
1917+
});
1918+
});
18261919
});

0 commit comments

Comments
 (0)