环回 – 包含关系的计算属性

我有一个回环应用程序,我希望能够包含API调用关系的计算属性。 例如,假设我有一个apartment模型和一个address模型。 address有属性city state

我想打一个电话,到公寓模型,并把城市和州作为一个单一的string从相关的address模型。

我从@Raymond Feng的这个问题的答案中得到了一些启发,并尝试了下面的方法(借口coffeescript /伪代码):

 address.defineProperty(address.prototype, "fullAddress", get: () -> return address.city + " " + address.state ) 

但是,当我尝试:

 apartment.findOne({ include: relation: "address" scope: fields: fullAddress: true }, (err, apartment) -> console.log(apartment) ) 

我明白了

 Error: ER_BAD_FIELD_ERROR: Unknown column 'fullAddress' in 'field list' 

值得注意的是,当我试图查询地址模型而不指定字段时,我得到一个名为'[object Object]'的值为null的属性,我怀疑这是我尝试定义fullAddress属性的结果。

我认为我用错误的语法来解决这个问题。 我正在寻找可能,如果是的话,我该怎么做?

(不再是)Loopback不支持计算属性。

这可以通过使用Loopback的操作钩子来完成,如下所述: dynamic属性或环回模型中的聚合函数

Loopback 缺less对依赖于相关模型的计算属性的支持 ,因为相关模型是asynchronous加载的。 但是,我写了一个解决scheme来解决这个问题(请原谅coffeescript):

 app.wrapper = (model, fn, args)-> deferred = Q.defer() args.push((err, result)-> console.log(err) if err throw err if err deferred.resolve(result) ) app.models[model][fn].apply(app.models[model], args) return deferred.promise app.mixCalcs = (model, fn, args)-> mainDeferred = Q.defer() iterationDeferreds = new Array() mixinCalcs = (model, relationHash) -> #iterate if there if the model includes relations if relationHash.scope? and relationHash.scope.include? #test if hash includes multiple relations if typeof relationHash.scope.include == "array" _.each(relationHash.scope.include, (subRelationHash) -> mixinCalcs(model[subRelationHash.relation](), subRelationHash) ) else mixinCalcs(model[relationHash.scope.include.relation](), relationHash.scope.include) #iterate if the model to be unpacked is an array (toMany relationship) if model[0]? _.each(model, (subModel) -> mixinCalcs(subModel, relationHash) ) #we're done with this model, we don't want to mix anything into it return #check if the hash requests the inclusion of calcs if relationHash.scope? and relationHash.scope.calc? #setup deferreds because we will be loading things iterationDeferred = Q.defer() iterationDeferreds.push(iterationDeferred.promise) calc = relationHash.scope.calc #get the calcHash definition calcHash = app.models[model.constructor.definition.name]["calcHash"] #here we use a pair of deferreds. Inner deferrds load the reiquirements for each calculated val #outer deferreds fire once all inner deferred deps are loaded to caluclate each val #once all vals are calced the iteration deferred fires, resolving this object in the query #once all iteration deferreds fire, we can send back the query through main deferred outerDeferreds = new Array() for k, v of calcHash if calc[k] ((k, v) -> outerDeferred = Q.defer() outerDeferreds.push(outerDeferred.promise) innerDeferreds = new Array() #load each required relation, then resolve the inner promise _.each(v.required, (req) -> innerDeferred = Q.defer() innerDeferreds.push(innerDeferred.promise) model[req]((err, val) -> console.log("inner Deferred for #{req} of #{model.constructor.definition.name}") innerDeferred.resolve(val) ) ) #all relations loaded, calculate the value and return it through outer deferred Q.all(innerDeferreds).done((deps)-> ret = {} ret[k] = v.fn(model, deps) console.log("outer Deferred for #{k} of #{model.constructor.definition.name}") outerDeferred.resolve(ret) ) )(k, v) #all calculations complete, mix them into the model Q.all(outerDeferreds).done((deps)-> _.each(deps, (dep)-> for k, v of dep model[k] = v ) console.log("iteration Deferred for #{model.constructor.definition.name}") iterationDeferred.resolve() ) #/end iterate() app.wrapper(model, fn, args).done((model) -> mixinCalcs(model, {scope: args[0]}) console.log(iterationDeferreds) #all models have been completed Q.all(iterationDeferreds).done(()-> console.log("main Deferred") mainDeferred.resolve(model) ) ) return mainDeferred.promise 

编译的Javascript(没有评论):

  app.wrapper = function(model, fn, args) { var deferred; deferred = Q.defer(); args.push(function(err, result) { if (err) { console.log(err); } if (err) { throw err; } return deferred.resolve(result); }); app.models[model][fn].apply(app.models[model], args); return deferred.promise; }; app.mixCalcs = function(model, fn, args) { var iterationDeferreds, mainDeferred, mixinCalcs; mainDeferred = Q.defer(); iterationDeferreds = new Array(); mixinCalcs = function(model, relationHash) { var calc, calcHash, iterationDeferred, k, outerDeferreds, v; if ((relationHash.scope != null) && (relationHash.scope.include != null)) { if (typeof relationHash.scope.include === "array") { _.each(relationHash.scope.include, function(subRelationHash) { return mixinCalcs(model[subRelationHash.relation](), subRelationHash); }); } else { mixinCalcs(model[relationHash.scope.include.relation](), relationHash.scope.include); } } if (model[0] != null) { _.each(model, function(subModel) { return mixinCalcs(subModel, relationHash); }); return; } if ((relationHash.scope != null) && (relationHash.scope.calc != null)) { iterationDeferred = Q.defer(); iterationDeferreds.push(iterationDeferred.promise); calc = relationHash.scope.calc; calcHash = app.models[model.constructor.definition.name]["calcHash"]; outerDeferreds = new Array(); for (k in calcHash) { v = calcHash[k]; if (calc[k]) { (function(k, v) { var innerDeferreds, outerDeferred; outerDeferred = Q.defer(); outerDeferreds.push(outerDeferred.promise); innerDeferreds = new Array(); _.each(v.required, function(req) { var innerDeferred; innerDeferred = Q.defer(); innerDeferreds.push(innerDeferred.promise); return model[req](function(err, val) { console.log("inner Deferred for " + req + " of " + model.constructor.definition.name); return innerDeferred.resolve(val); }); }); return Q.all(innerDeferreds).done(function(deps) { var ret; ret = {}; ret[k] = v.fn(model, deps); console.log("outer Deferred for " + k + " of " + model.constructor.definition.name); return outerDeferred.resolve(ret); }); })(k, v); } } return Q.all(outerDeferreds).done(function(deps) { _.each(deps, function(dep) { var _results; _results = []; for (k in dep) { v = dep[k]; _results.push(model[k] = v); } return _results; }); console.log("iteration Deferred for " + model.constructor.definition.name); return iterationDeferred.resolve(); }); } }; app.wrapper(model, fn, args).done(function(model) { mixinCalcs(model, { scope: args[0] }); console.log(iterationDeferreds); return Q.all(iterationDeferreds).done(function() { console.log("main Deferred"); return mainDeferred.resolve(model); }); }); return mainDeferred.promise; }; 

插件取决于Q和下划线,所以你需要包含这些库。 上面的主要代码应该被加载到启动脚本中。 使用以下语法在模型的js定义文件中定义计算的属性:

 MODEL_NAME.calcHash = { "ATTRIBUTE_NAME": required: ["REQUIRED", "RELATION", "MODEL", "NAMES"] fn: (model, deps) -> #function which should return the calculated value. Loaded relations are provided as an array to the deps arg return deps[0].value + deps[1].value + deps[2].value "ATTRIBUTE_TWO": #... } 

使用以下语法调用插件:

 app.mixCalcs("MODEL_NAME", "FUNCTION_NAME (ie 'findOne')", [arguments for the called function]) 

您的filter现在支持属性calc ,它的function类似于fields ,除了它将包含来自calcHash的计算属性。

用法示例:

 query = Candidate.app.mixCalcs("Candidate", "findOne", [{ where: id: 1 include: relation: "user" scope: calc: timeSinceLastLogin: true calc: fullName: true }]) query.done((result)-> cb(null, result) ) 

如果来自回送团队的人能够将这些行的特征合并到主要版本中,那将是非常好的。 我也打开了一个回环问题 。

你可以尝试一些很好的混合,这是我的collections:

你可以尝试第三方插件:

1)用于聚合的环回连接器: https : //github.com/benkroeger/loopback-connector-aggregate

2)计算/计算属性的环回混合(仅在创build新模型实例时有效): https : //github.com/fullcube/loopback-ds-calculated-mixin https://github.com/fullcube/loopback-ds-计算,混入

3)更改跟踪的环回混合(启动每个更新): https : //github.com/fullcube/loopback-ds-changed-mixin

4)如果你需要一个统计信息 – 这里是另一个混​​合: https : //github.com/jonathan-casarrubias/loopback-stats-mixin

5)你可以计数相关的模型: https : //github.com/exromany/loopback-counts-mixin

6)你可以自动非规范化和保存相关数据,并select将存储的字段(有用的caching): https : //github.com/jbmarchetti/loopback-denormalize

7)如果在导入期间需要计算字段映射的属性: https : //github.com/jonathan-casarrubias/loopback-import-mixin