Coverage

88%
367
324
43

manikin.js

88%
367
324
43
LineHitsSource
1// Generated by CoffeeScript 1.4.0
21(function() {
31 var ObjectId, async, mongojs, mongoose, _,
4 __slice = [].slice;
5
61 async = require('async');
7
81 _ = require('underscore');
9
101 mongoose = require('mongoose');
11
121 mongojs = require('mongojs');
13
141 ObjectId = mongoose.Schema.ObjectId;
15
161 _.mixin(require('underscore.plus'));
17
181 exports.create = function() {
1917 var api, db, defModel, getKeys, getManyToMany, getMetaFields, getOwnedModels, getOwners, internalListSub, massage, massageCore, massageOne, massaged, meta, model, models, nullablesValidation, preRemoveCascadeNonNullable, preRemoveCascadeNullable, preprocFilter, propagate, specTransform;
2017 db = null;
2117 api = {};
2217 models = {};
2317 meta = {};
2417 propagate = function(callback, f) {
259 return function() {
269 var args, err;
279 err = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
289 if (err) {
290 return callback(err);
30 } else {
319 return f.apply(this, args);
32 }
33 };
34 };
3517 model = function(name, schema) {
3625 var ss;
3725 ss = new mongoose.Schema(schema, {
38 strict: true
39 });
4025 ss.set('versionKey', false);
4125 return mongoose.model(name, ss);
42 };
4317 api.isValidId = function(id) {
442 try {
452 mongoose.mongo.ObjectID(id);
461 return true;
47 } catch (ex) {
481 return false;
49 }
50 };
5117 preprocFilter = function(filter) {
5231 var x;
5331 x = _.extend({}, filter);
5431 if (x.id) {
558 x._id = x.id;
56 }
5731 delete x.id;
5831 return x;
59 };
6017 massageOne = function(x) {
6152 if (!(x != null)) {
620 return x;
63 }
6452 x.id = x._id;
6552 delete x._id;
6652 return x;
67 };
6817 massageCore = function(r2) {
6954 if (Array.isArray(r2)) {
7019 return r2.map(massageOne);
71 } else {
7235 return massageOne(r2);
73 }
74 };
7517 massage = function(r2) {
7654 return massageCore(JSON.parse(JSON.stringify(r2)));
77 };
7817 massaged = function(f) {
7934 return function(err, data) {
8034 if (err) {
812 return f(err);
82 } else {
8332 return f(null, massage(data));
84 }
85 };
86 };
8717 api.connect = function(databaseUrl, callback) {
8810 mongoose.connect(databaseUrl);
8910 db = mongojs.connect(databaseUrl, api.getModels());
9010 return db[Object.keys(models)[0]].find(function(err) {
9110 return callback(err);
92 });
93 };
9417 api.close = function(callback) {
9510 mongoose.connection.close();
9610 return callback();
97 };
9817 api.list = function(model, filter, callback) {
9911 var rr;
10011 filter = preprocFilter(filter);
10111 rr = models[model].find(filter);
10211 if (meta[model].defaultSort != null) {
1031 rr = rr.sort(_.object([[meta[model].defaultSort, 'asc']]));
104 }
10511 return rr.exec(massaged(callback));
106 };
10717 api.getOne = function(model, config, callback) {
10814 var filter;
10914 filter = preprocFilter(config.filter);
11014 return models[model].findOne(filter, function(err, data) {
11114 if (err) {
1121 if (err.toString() === 'Error: Invalid ObjectId') {
1131 callback(new Error('No such id'));
114 } else {
1150 callback(err);
116 }
11713 } else if (!(data != null)) {
1181 return callback(new Error('No match'));
119 } else {
12012 return callback(null, massage(data));
121 }
122 });
123 };
12417 api.delOne = function(model, filter, callback) {
1252 filter = preprocFilter(filter);
1262 return models[model].findOne(filter, function(err, d) {
1272 if (err) {
1281 if (err.toString() === 'Error: Invalid ObjectId') {
1291 return callback(new Error('No such id'));
130 } else {
1310 return callback(err);
132 }
1331 } else if (!(d != null)) {
1340 return callback(new Error('No such id'));
135 } else {
1361 return d.remove(function(err) {
1371 return callback(err, !err ? massage(d) : void 0);
138 });
139 }
140 });
141 };
14217 getKeys = function(data, target, prefix) {
1432 var valids;
1442 if (target == null) {
1451 target = [];
146 }
1472 if (prefix == null) {
1481 prefix = '';
149 }
1502 valids = ['Array', 'String', 'Boolean', 'Date', 'Number', 'Null'];
1512 Object.keys(data).forEach(function(key) {
1526 if (valids.some(function(x) {
15320 return _(data[key])['is' + x]();
154 })) {
1555 return target.push(prefix + key);
156 } else {
1571 return getKeys(data[key], target, prefix + key + '.');
158 }
159 });
1602 return target;
161 };
16217 api.putOne = function(modelName, data, filter, callback) {
1631 var inputFields, inputFieldsValid, invalidFields, validField;
1641 filter = preprocFilter(filter);
1651 model = models[modelName];
1661 inputFieldsValid = getKeys(data);
1671 inputFields = Object.keys(data);
1681 validField = Object.keys(model.schema.paths);
1691 invalidFields = _.difference(inputFieldsValid, validField);
1701 if (invalidFields.length > 0) {
1710 callback(new Error("Invalid fields: " + invalidFields.join(', ')));
1720 return;
173 }
1741 return model.findOne(filter, propagate(callback, function(d) {
1751 if (!(d != null)) {
1760 callback(new Error("No such id"));
1770 return;
178 }
1791 inputFields.forEach(function(key) {
1804 return d[key] = data[key];
181 });
1821 return d.save(function(err) {
1831 return callback(err, err ? null : massage(d));
184 });
185 }));
186 };
18717 api.post = function(model, indata, callback) {
18823 var owners, ownersOwners, ownersRaw, saveFunc;
18923 saveFunc = function(data) {
19023 return new models[model](data).save(function(err) {
19123 var fieldMatch, valueMatch;
19223 if (err && err.code === 11000) {
1930 fieldMatch = err.err.match(/\$([a-zA-Z]+)_1/);
1940 valueMatch = err.err.match(/"([a-zA-Z]+)"/);
1950 if (fieldMatch && valueMatch) {
1960 return callback(new Error("Duplicate value '" + valueMatch[1] + "' for " + fieldMatch[1]));
197 } else {
1980 return callback(new Error("Unique constraint violated"));
199 }
200 } else {
20123 return massaged(callback).apply(this, arguments);
202 }
203 });
204 };
20523 ownersRaw = getOwners(model);
20623 owners = _(ownersRaw).pluck('plur');
20723 ownersOwners = _(owners.map(function(x) {
2087 return getOwners(x);
209 })).flatten();
21023 if (ownersOwners.length === 0) {
21120 return saveFunc(indata);
212 } else {
2133 return api.getOne(owners[0], {
214 id: indata[ownersRaw[0].sing]
215 }, function(err, ownerdata) {
2163 var metaFields, paths;
2173 paths = models[owners[0]].schema.paths;
2183 metaFields = Object.keys(paths).filter(function(key) {
21914 return !!paths[key].options['x-owner'] || !!paths[key].options['x-indirect-owner'];
220 });
2213 metaFields.forEach(function(key) {
2224 return indata[key] = ownerdata[key];
223 });
2243 return saveFunc(indata);
225 });
226 }
227 };
22817 internalListSub = function(model, outer, id, filter, callback) {
2293 var finalFilter;
2303 if (!(callback != null)) {
2313 callback = filter;
2323 filter = {};
233 }
2343 if ((filter[outer] != null) && filter[outer].toString() !== id.toString()) {
2350 callback(new Error('No such id'));
2360 return;
237 }
2383 filter = preprocFilter(filter);
2393 finalFilter = _.extend({}, filter, _.object([[outer, id]]));
2403 return models[model].find(finalFilter, callback);
241 };
24217 api.delMany = function(primaryModel, primaryId, propertyName, secondaryId, callback) {
2431 var inverseName, mm, secondaryModel;
2441 mm = getManyToMany(primaryModel).filter(function(x) {
2451 return x.name === propertyName;
246 })[0];
2471 if (mm === null) {
2480 callback(new Error('Invalid manyToMany-property'));
2490 return;
250 }
2511 secondaryModel = mm.ref;
2521 inverseName = mm.inverseName;
2531 return async.forEach([
254 {
255 model: primaryModel,
256 id: primaryId,
257 property: propertyName,
258 secondaryId: secondaryId
259 }, {
260 model: secondaryModel,
261 id: secondaryId,
262 property: inverseName,
263 secondaryId: primaryId
264 }
265 ], function(item, callback) {
2662 return models[item.model].findById(item.id, propagate(callback, function(data) {
2672 var conditions, options, update;
2682 conditions = {
269 _id: item.id
270 };
2712 update = {
272 $pull: _.object([[item.property, item.secondaryId]])
273 };
2742 options = {};
2752 return models[item.model].update(conditions, update, options, function(err, numAffected) {
2762 return callback(err);
277 });
278 }));
279 }, callback);
280 };
28117 api.postMany = function(primaryModel, primaryId, propertyName, secondaryId, callback) {
2822 var inverseName, mm, secondaryModel;
2832 mm = getManyToMany(primaryModel).filter(function(x) {
2842 return x.name === propertyName;
285 })[0];
2862 if (mm === null) {
2870 callback(new Error('Invalid manyToMany-property'));
2880 return;
289 }
2902 secondaryModel = mm.ref;
2912 inverseName = mm.inverseName;
2922 return models[primaryModel].findById(primaryId, propagate(callback, function(data) {
2932 return models[secondaryModel].findById(secondaryId, propagate(callback, function(data2) {
2942 if (-1 === data[propertyName].indexOf(secondaryId)) {
2952 data[propertyName].push(secondaryId);
296 }
2972 if (-1 === data2[inverseName].indexOf(primaryId)) {
2982 data2[inverseName].push(primaryId);
299 }
3002 return data.save(propagate(callback, function() {
3012 return data2.save(function(err) {
3022 return callback(err, {});
303 });
304 }));
305 }));
306 }));
307 };
30817 api.getMany = function(primaryModel, primaryId, propertyName, callback) {
3098 return models[primaryModel].findOne({
310 _id: primaryId
311 }).populate(propertyName).exec(function(err, story) {
3128 return callback(err, massage(story[propertyName]));
313 });
314 };
31517 specTransform = function(allspec, modelName, tgt, src, keys) {
31637 return keys.forEach(function(key) {
31750 var obj;
31850 if (typeof src[key] === 'string') {
31910 obj = {};
32010 obj[key] = {
321 type: src[key]
322 };
32310 return specTransform(allspec, modelName, tgt, obj, [key]);
32440 } else if (!(src[key].type != null)) {
3250 throw new Error("must assign a type: " + JSON.stringify(keys));
32640 } else if (src[key].type === 'mixed') {
3272 return tgt[key] = {
328 type: mongoose.Schema.Types.Mixed
329 };
33038 } else if (src[key].type === 'nested') {
3311 tgt[key] = {};
3321 return specTransform(allspec, modelName, tgt[key], src[key], _.without(Object.keys(src[key]), 'type'));
33337 } else if (src[key].type === 'string') {
33428 tgt[key] = {
335 type: String,
336 required: !!src[key].required
337 };
33828 if (src[key]["default"] != null) {
33918 tgt[key]["default"] = src[key]["default"];
340 }
34128 if (src[key].index != null) {
3420 tgt[key].index = src[key].index;
343 }
34428 if (src[key].unique != null) {
3450 tgt[key].unique = src[key].unique;
346 }
34728 if (src[key].validate != null) {
3481 return tgt[key].validate = function(value, callback) {
3492 return src[key].validate(api, value, callback);
350 };
351 }
3529 } else if (src[key].type === 'number') {
3533 tgt[key] = {
354 type: Number,
355 required: !!src[key].required
356 };
3573 if (src[key]["default"] != null) {
3580 tgt[key]["default"] = src[key]["default"];
359 }
3603 if (src[key].index != null) {
3610 tgt[key].index = src[key].index;
362 }
3633 if (src[key].unique != null) {
3640 return tgt[key].unique = src[key].unique;
365 }
3666 } else if (src[key].type === 'date') {
3671 tgt[key] = {
368 type: Date,
369 required: !!src[key].required
370 };
3711 if (src[key]["default"] != null) {
3720 tgt[key]["default"] = src[key]["default"];
373 }
3741 if (src[key].index != null) {
3750 tgt[key].index = src[key].index;
376 }
3771 if (src[key].unique != null) {
3780 return tgt[key].unique = src[key].unique;
379 }
3805 } else if (src[key].type === 'boolean') {
3811 tgt[key] = {
382 type: Boolean,
383 required: !!src[key].required
384 };
3851 if (src[key]["default"] != null) {
3860 tgt[key]["default"] = src[key]["default"];
387 }
3881 if (src[key].index != null) {
3890 tgt[key].index = src[key].index;
390 }
3911 if (src[key].unique != null) {
3920 return tgt[key].unique = src[key].unique;
393 }
3944 } else if (src[key].type === 'hasOne') {
3950 return tgt[key] = {
396 ref: src[key].model,
397 'x-validation': src[key].validation
398 };
3994 } else if (src[key].type === 'hasMany') {
4003 tgt[key] = [
401 {
402 type: ObjectId,
403 ref: src[key].model,
404 inverseName: src[key].inverseName || key
405 }
406 ];
4073 return allspec[src[key].model][src[key].inverseName || key] = [
408 {
409 type: ObjectId,
410 ref: modelName,
411 inverseName: key
412 }
413 ];
414 } else {
4151 throw new Error("Invalid type: " + src[key].type);
416 }
417 });
418 };
41917 api.defModels = function(models) {
42014 var allspec, rest;
42114 allspec = {};
42214 Object.keys(models).forEach(function(modelName) {
42326 return allspec[modelName] = {};
424 });
42514 rest = {};
42614 Object.keys(models).forEach(function(modelName) {
42726 var inspec, owners, spec;
42826 spec = allspec[modelName];
42926 owners = models[modelName].owners || {};
43026 inspec = models[modelName].fields || {};
43126 specTransform(allspec, modelName, spec, inspec, Object.keys(inspec));
43225 return rest[modelName] = {
433 owners: owners,
434 spec: spec
435 };
436 });
43713 return Object.keys(models).forEach(function(modelName) {
43825 return defModel(modelName, models[modelName], rest[modelName].spec, rest[modelName].owners);
439 });
440 };
44117 defModel = function(name, conf, spec, owners) {
44225 Object.keys(owners).forEach(function(ownerName) {
4439 return spec[ownerName] = {
444 type: ObjectId,
445 ref: owners[ownerName],
446 required: true,
447 'x-owner': true
448 };
449 });
45025 Object.keys(owners).forEach(function(ownerName) {
4519 var paths;
4529 paths = models[owners[ownerName]].schema.paths;
4539 return Object.keys(paths).filter(function(p) {
45424 return paths[p].options['x-owner'] || paths[p].options['x-indirect-owner'];
455 }).forEach(function(p) {
4564 return spec[p] = {
457 type: ObjectId,
458 ref: paths[p].options.ref,
459 required: true,
460 'x-indirect-owner': true
461 };
462 });
463 });
46425 Object.keys(spec).forEach(function(fieldName) {
46553 if (spec[fieldName].ref != null) {
46613 return spec[fieldName].type = ObjectId;
467 }
468 });
46925 meta[name] = {
470 defaultSort: conf.defaultSort
471 };
47225 models[name] = model(name, spec);
47325 models[name].schema.pre('save', nullablesValidation(models[name].schema));
47425 models[name].schema.pre('remove', function(next) {
4753 return preRemoveCascadeNonNullable(models[name], this._id.toString(), next);
476 });
47725 return models[name].schema.pre('remove', function(next) {
4783 return preRemoveCascadeNullable(models[name], this._id.toString(), next);
479 });
480 };
48117 getMetaFields = function(modelName) {
4823 var metaFields, paths, typeFunc, typeMap;
4833 typeMap = {
484 ObjectID: 'string',
485 String: 'string',
486 Number: 'number',
487 Boolean: 'boolean',
488 Date: 'date'
489 };
4903 paths = models[modelName].schema.paths;
4913 typeFunc = function(x) {
4920 if (x === Boolean) {
4930 return 'boolean';
494 }
4950 if (x === Date) {
4960 return 'date';
497 }
498 };
4993 metaFields = Object.keys(paths).filter(function(key) {
50010 return !Array.isArray(paths[key].options.type);
501 }).map(function(key) {
5028 return {
503 name: (key === '_id' ? 'id' : key),
504 readonly: key === '_id' || !!paths[key].options['x-owner'] || !!paths[key].options['x-indirect-owner'],
505 required: !!paths[key].options.required,
506 type: typeMap[paths[key].instance] || typeFunc(paths[key].options.type) || 'unknown'
507 };
508 });
5093 return _.sortBy(metaFields, 'name');
510 };
51117 getOwners = function(modelName) {
51233 var outers, paths;
51333 paths = models[modelName].schema.paths;
51433 outers = Object.keys(paths).filter(function(x) {
515112 return paths[x].options['x-owner'];
516 }).map(function(x) {
51711 return {
518 plur: paths[x].options.ref,
519 sing: x
520 };
521 });
52233 return outers;
523 };
52417 getOwnedModels = function(ownerModelName) {
5256 return _.flatten(Object.keys(models).map(function(modelName) {
52621 var paths;
52721 paths = models[modelName].schema.paths;
52821 return Object.keys(paths).filter(function(x) {
52978 return paths[x].options.type === ObjectId && paths[x].options.ref === ownerModelName && paths[x].options['x-owner'];
530 }).map(function(x) {
5313 return {
532 name: modelName,
533 field: x
534 };
535 });
536 }));
537 };
53817 getManyToMany = function(modelName) {
5396 var manyToMany, paths;
5406 paths = models[modelName].schema.paths;
5416 manyToMany = Object.keys(paths).filter(function(x) {
54219 return Array.isArray(paths[x].options.type);
543 }).map(function(x) {
5445 return {
545 inverseName: paths[x].options.type[0].inverseName,
546 ref: paths[x].options.type[0].ref,
547 name: x
548 };
549 });
5506 return manyToMany;
551 };
55217 api.getMeta = function(modelName) {
5533 return {
554 fields: getMetaFields(modelName),
555 owns: getOwnedModels(modelName),
556 owners: getOwners(modelName),
557 manyToMany: getManyToMany(modelName)
558 };
559 };
56017 api.getModels = function() {
56112 return Object.keys(models);
562 };
56317 nullablesValidation = function(schema) {
56425 return function(next) {
56535 var nonNullOuters, outers, paths, self;
56635 self = this;
56735 paths = schema.paths;
56835 outers = Object.keys(paths).filter(function(x) {
569116 return paths[x].options.type === ObjectId && typeof paths[x].options.ref === 'string' && !paths[x].options['x-owner'];
570 }).map(function(x) {
5714 return {
572 plur: paths[x].options.ref,
573 sing: x,
574 validation: paths[x].options['x-validation']
575 };
576 });
57735 nonNullOuters = outers.filter(function(x) {
5784 return self[x.sing] != null;
579 });
58035 return async.forEach(nonNullOuters, function(o, callback) {
5814 return api.getOne(o.plur, {
582 id: self[o.sing]
583 }, function(err, data) {
5844 if (err || !data) {
5850 return callback(new Error("Invalid pointer"));
5864 } else if (o.validation) {
5870 return o.validation(self, data, function(err) {
5880 return callback(err ? new Error(err) : void 0);
589 });
590 } else {
5914 return callback();
592 }
593 });
594 }, next);
595 };
596 };
59717 preRemoveCascadeNonNullable = function(owner, id, next) {
5983 var flattenedModels;
5993 flattenedModels = getOwnedModels(owner.modelName);
6003 return async.forEach(flattenedModels, function(mod, callback) {
6012 return internalListSub(mod.name, mod.field, id, function(err, data) {
6022 return async.forEach(data, function(item, callback) {
6032 return item.remove(callback);
604 }, callback);
605 });
606 }, next);
607 };
60817 preRemoveCascadeNullable = function(owner, id, next) {
6093 var flattenedModels, ownedModels;
6103 ownedModels = Object.keys(models).map(function(modelName) {
61112 var paths;
61212 paths = models[modelName].schema.paths;
61312 return Object.keys(paths).filter(function(x) {
61448 return paths[x].options.type === ObjectId && paths[x].options.ref === owner.modelName && !paths[x].options['x-owner'];
615 }).map(function(x) {
6161 return {
617 name: modelName,
618 field: x
619 };
620 });
621 });
6223 flattenedModels = _.flatten(ownedModels);
6233 return async.forEach(flattenedModels, function(mod, callback) {
6241 return internalListSub(mod.name, mod.field, id, function(err, data) {
6251 return async.forEach(data, function(item, callback) {
6260 item[mod.field] = null;
6270 item.save();
6280 return callback();
629 }, callback);
630 });
631 }, next);
632 };
63317 return api;
634 };
635
636}).call(this);