|
| 1 | +module.exports = (Model, options) => { |
| 2 | + |
| 3 | + /** |
| 4 | + * trying to address the following include filter |
| 5 | + * { |
| 6 | + * "include" : [ |
| 7 | + * { |
| 8 | + * "relation":"parameters", |
| 9 | + * "scope": { |
| 10 | + * "where":{ |
| 11 | + * "and": [ |
| 12 | + * {"name":"chemical_formula"}, |
| 13 | + * {"value":"Cu"} |
| 14 | + * ] |
| 15 | + * } |
| 16 | + * } |
| 17 | + * }, |
| 18 | + * { |
| 19 | + * "relation":"parameters", |
| 20 | + * "scope": { |
| 21 | + * "where":{ |
| 22 | + * "and": [ |
| 23 | + * {"name":"sample_state"}, |
| 24 | + * {"value":"solid"} |
| 25 | + * ] |
| 26 | + * } |
| 27 | + * } |
| 28 | + * } |
| 29 | + * ] |
| 30 | + * } |
| 31 | + * |
| 32 | + * one liner |
| 33 | + * {"include":[{"relation":"parameters","scope":{"where":{"and": [{"name":"chemical_formula"},{"value":"Cu"}]}}},{"relation":"parameters","scope":{"where":{"and": [{"name":"sample_state"},{"value":"solid"}]}}}]} |
| 34 | + */ |
| 35 | + |
| 36 | + // we intercept the find command to check the filter on parameters |
| 37 | + Model.beforeRemote('find', (ctx, unused, next) => { |
| 38 | + |
| 39 | + const filter = ctx.args.filter ? ctx.args.filter : {}; |
| 40 | + const [parametersRelations, newFilter] = getParametersRelations(filter); |
| 41 | + |
| 42 | + // we change filter and save the parameters relations |
| 43 | + // only if we have found parameters relations |
| 44 | + if (parametersRelations) { |
| 45 | + dSet(ctx, "data.relations.parameters", parametersRelations); |
| 46 | + ctx.args.filter = newFilter; |
| 47 | + } |
| 48 | + |
| 49 | + next(); |
| 50 | + }); |
| 51 | + |
| 52 | + // we intercept the find command to check the filter on parameters |
| 53 | + Model.afterRemote('find', (ctx, result, next) => { |
| 54 | + |
| 55 | + const parametersFilters = ctx.data.relations.parameters ? ctx.data.relations.parameters : []; |
| 56 | + |
| 57 | + if (parametersFilters) { |
| 58 | + ctx.result = filterOnParameters(ctx.result, parametersFilters); |
| 59 | + } |
| 60 | + |
| 61 | + next(); |
| 62 | + }); |
| 63 | + |
| 64 | +}; |
| 65 | + |
| 66 | +/** |
| 67 | + * extract parameters relations and modify filter to extract all parameters |
| 68 | + * @param {object} filter LoopBack filter object |
| 69 | + * @returns {array} Parameters relations array |
| 70 | + * @returns {object} Modified LoopBack object filter |
| 71 | + */ |
| 72 | +const getParametersRelations = (filter) => { |
| 73 | + |
| 74 | + // remove filter on parameter |
| 75 | + const newFilter = JSON.parse(JSON.stringify(filter)); |
| 76 | + newFilter.include = filter.include |
| 77 | + ? filter.include |
| 78 | + .filter( |
| 79 | + (primary) => (primary.relation != "parameters") |
| 80 | + ) |
| 81 | + : []; |
| 82 | + |
| 83 | + // extract filter on parameter |
| 84 | + const parametersRelations = filter.include |
| 85 | + ? filter.include |
| 86 | + .filter( |
| 87 | + (primary) => (primary.relation == "parameters") |
| 88 | + ) |
| 89 | + : []; |
| 90 | + // if we have a filter on parameter |
| 91 | + // adds the relation with parameter, so we can retrieve all of them |
| 92 | + if (parametersRelations.length > 0) { |
| 93 | + newFilter.include.push({ "relation": "parameters" }); |
| 94 | + |
| 95 | + } |
| 96 | + return [ |
| 97 | + parametersRelations.map((item) => { |
| 98 | + /* |
| 99 | + let simpleItem = {}; |
| 100 | + item.scope.where.and.forEach((condition) => { |
| 101 | + simpleItem = { |
| 102 | + ...simpleItem, |
| 103 | + ...condition |
| 104 | + } |
| 105 | + }) |
| 106 | + return simpleItem; |
| 107 | + */ |
| 108 | + return item.scope.where; |
| 109 | + }), |
| 110 | + newFilter]; |
| 111 | +} |
| 112 | + |
| 113 | +/** |
| 114 | + * extract results that matches all the parameters filters |
| 115 | + * @param {object} filter LoopBack filter object |
| 116 | + * @returns {array} Parameters relations array |
| 117 | + * @returns {object} Modified LoopBack object filter |
| 118 | + */ |
| 119 | +const filterOnParameters = (result, parametersFilter) => { |
| 120 | + |
| 121 | + const filteredResults = result.filter((item) => { |
| 122 | + // we check if each item match all the conditions |
| 123 | + return parametersFilter.every( |
| 124 | + f => item['__data']['parameters'].some( |
| 125 | + p => cop(f, p['__data']))) |
| 126 | + // p => Object.keys(f).every( |
| 127 | + // k => condition(p[k], f[k])))) |
| 128 | + }) |
| 129 | + |
| 130 | + return filteredResults; |
| 131 | +} |
| 132 | + |
| 133 | +/** |
| 134 | + * condition operator |
| 135 | + * @param {*} c - condition |
| 136 | + * @param {*} v - values on which apply the condition |
| 137 | + * @returns {boolean} true if the condition is met |
| 138 | + */ |
| 139 | +const cop = (c, v) => { |
| 140 | + if (Object.prototype.toString.call(c) === '[object Object]') { |
| 141 | + const k = Object.keys(c)[0]; |
| 142 | + switch (k) { |
| 143 | + case 'or': |
| 144 | + return c.or.some(sc => cop(sc, v)); |
| 145 | + break; |
| 146 | + case 'and': |
| 147 | + return c.and.every(sc => cop(sc, v)); |
| 148 | + break; |
| 149 | + } |
| 150 | + // the k is a property of the object |
| 151 | + if (Object.prototype.toString.call(c[k]) === '[object Object]') { |
| 152 | + const ck = Object.keys(c[k])[0]; |
| 153 | + switch (ck) { |
| 154 | + case 'between': |
| 155 | + return (v[k] >= c[k][ck][0]) && (v[k] <= c[k][ck][1]); |
| 156 | + break; |
| 157 | + case 'lt': |
| 158 | + return v[k] < c[k][ck]; |
| 159 | + break; |
| 160 | + case 'gt': |
| 161 | + return v[k] > c[k][ck]; |
| 162 | + break; |
| 163 | + } |
| 164 | + } |
| 165 | + // we need to compare a key of value with a key of condition |
| 166 | + return v[k] == c[k] |
| 167 | + } |
| 168 | + // normal element comparison |
| 169 | + return v == c; |
| 170 | +} |
| 171 | + |
| 172 | + |
| 173 | +/** |
| 174 | + * Dynamically sets a deeply nested value in an object. |
| 175 | + * @function |
| 176 | + * @param {!object} obj - The object which contains the value you want to change/set. |
| 177 | + * @param {!array|string} inPath - Array or string representation of path to the value you want to change/set. |
| 178 | + * @param {!mixed} value - The value you want to set it to. |
| 179 | + * @param {boolean} setrecursively - If true, will set value of non-existing path as well. |
| 180 | + */ |
| 181 | +function dSet(obj, inPath, value, setrecursively = true) { |
| 182 | + const path = |
| 183 | + Array.isArray(inPath) |
| 184 | + ? inPath |
| 185 | + : ( |
| 186 | + (typeof inPath === 'string' || inPath instanceof String) |
| 187 | + ? inPath.split(".") |
| 188 | + : []); |
| 189 | + return path.reduce( |
| 190 | + (a, b, level) => { |
| 191 | + if (setrecursively && typeof a[b] === "undefined" && level !== (path.length - 1)) { |
| 192 | + a[b] = {}; |
| 193 | + } |
| 194 | + |
| 195 | + if (level === (path.length - 1)) { |
| 196 | + a[b] = value; |
| 197 | + return value; |
| 198 | + } |
| 199 | + return a[b]; |
| 200 | + }, |
| 201 | + obj |
| 202 | + ); |
| 203 | +} |
0 commit comments