123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- // ███████╗ █████╗ ██████╗██╗ ██╗ ██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ██████╗
- // ██╔════╝██╔══██╗██╔════╝██║ ██║ ██╔══██╗██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔══██╗
- // █████╗ ███████║██║ ███████║ ██████╔╝█████╗ ██║ ██║ ██║██████╔╝██║ ██║
- // ██╔══╝ ██╔══██║██║ ██╔══██║ ██╔══██╗██╔══╝ ██║ ██║ ██║██╔══██╗██║ ██║
- // ███████╗██║ ██║╚██████╗██║ ██║ ██║ ██║███████╗╚██████╗╚██████╔╝██║ ██║██████╔╝
- // ╚══════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝
- //
- // ██████╗ ███████╗███████╗██████╗
- // ██╔══██╗██╔════╝██╔════╝██╔══██╗
- // ██║ ██║█████╗ █████╗ ██████╔╝
- // ██║ ██║██╔══╝ ██╔══╝ ██╔═══╝
- // ██████╔╝███████╗███████╗██║
- // ╚═════╝ ╚══════╝╚══════╝╚═╝
- //
- /**
- * Module dependencies
- */
- var util = require('util');
- var _ = require('@sailshq/lodash');
- /*
- * eachRecordDeep()
- *
- * Iterate over an array of potentially-populated records, running the provided
- * iteratee function once per record, whether they are top-level (parent records)
- * or not (populated child records).
- *
- * Note that the iteratee always runs for any given parent record _before_ running
- * for any of the child records that it contains. This allows you to throw an error
- * or mutate the parent record before this iterator attempts to descend inside of it.
- *
- * Each parent record is assumed to be a dictionary, but beyond that, just about any
- * other sort of nonsense is completely ignored. The iteratee is only called for
- * singular associations if the value is at least a dictionary (e.g. if it is a number,
- * then this iterator turns a blind eye.)
- *
- * On the other hand, for _plural associations_, if the value is an array, the iteratee
- * is called once for each child record in the array- no matter WHAT data type those items
- * are. This is a deliberate choice for performance reasons, and it is up to whatever is
- * calling this utility to verify that array items are valid. (But note that this can easily
- * be done in the `iteratee`, when it runs for the containing parent record.)
- *
- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- *
- * @param {Array} records
- * An array of records.
- * > These might be normal logical records keyed by attribute name,
- * > or raw, physical-layer records ("pRecords") w/ column names
- * > instead of attribute names for its keys. Specify which kind of
- * > records these are using the `arePhysical` flag. If `arePhysical` is
- * > true, the child record of singular associations will be assumed to
- * > live under its column name instead of its attribute name. (And plural
- * > associations don't even have a "column name", so they're the same
- * > regardless.)
- *
- * @param {Function} iteratee
- * @param {Dictionary} record
- * @param {Ref} WLModel
- * @param {Number} depth
- * 1 - Parent record
- * 2 - Child record
- *
- * @param {Boolean} arePhysical
- * Whether or not these are physical-layer records keyed on column names.
- * For example, if using this utility in an adapter, pass in `true`.
- * Otherwise, use `false`. This is only relevant insofar as it affects
- * how singular associations are populated. If set to `true`, this indicates
- * that the populated child record dictionary for a singular association will
- * exist on the key for that association's `columnName` (vs. its attr name.)
- * > Regardless of what you put in here, be aware that the `tableName`
- * > should _never_ be relevant for the purposes of this utility. Any time
- * > `modelIdentity` is mentioned, that is exactly what is meant.
- *
- * @param {String} modelIdentity
- * The identity of the model these parent records came from (e.g. "pet" or "user")
- * > Useful for looking up the Waterline model and accessing its attribute definitions.
- * > Note that this is ALWAYS the model identity, and NEVER the table name.
- *
- * @param {Ref} orm
- * The Waterline ORM instance.
- * > Useful for accessing the model definitions.
- *
- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- */
- module.exports = function eachRecordDeep(records, iteratee, arePhysical, modelIdentity, orm) {
- if (!_.isArray(records)) {
- throw new Error('Consistency violation: Expected `records` to be an array. But instead, got: ' + util.inspect(records, { depth: 5 }) + '');
- }
- if (!_.isFunction(iteratee)) {
- throw new Error('Consistency violation: Expected `iteratee` to be a function. But instead, got: ' + util.inspect(iteratee, { depth: 5 }) + '');
- }
- if (!_.isString(modelIdentity) || modelIdentity === '') {
- throw new Error('Consistency violation: Expected `modelIdentity` to be a non-empty string. But instead, got: ' + util.inspect(modelIdentity, { depth: 5 }) + '');
- }
- // Grab dictionary of models (w/ backwards compatibility).
- var models = orm.collections !== undefined ? orm.collections : orm.models;
- // Look up the Waterline model for this query.
- // > This is so that we can reference the original model definition.
- // var ParentWLModel = getModel(modelIdentity, orm);
- var ParentWLModel = models[modelIdentity];
- // ┌─┐┌─┐┌─┐┬ ┬ ╦═╗╔═╗╔═╗╔═╗╦═╗╔╦╗ ┬┌┐┌ ┌─┐┬─┐┬─┐┌─┐┬ ┬
- // ├┤ ├─┤│ ├─┤ ╠╦╝║╣ ║ ║ ║╠╦╝ ║║ ││││ ├─┤├┬┘├┬┘├─┤└┬┘
- // └─┘┴ ┴└─┘┴ ┴ ╩╚═╚═╝╚═╝╚═╝╩╚══╩╝ ┴┘└┘ ┴ ┴┴└─┴└─┴ ┴ ┴
- // Loop over each parent record.
- _.each(records, function iterateRecords(record) {
- if (!_.isObject(record) || _.isArray(record) || _.isFunction(record)) {
- throw new Error('Consistency violation: Expected each item in the `records` array to be a record (a dictionary). But at least one of them is messed up. Record: ' + util.inspect(record, { depth: 5 }) + '');
- }
- // Call the iteratee for this parent record.
- iteratee(record, ParentWLModel, 1);
- // ┌─┐┌─┐┌─┐┬ ┬ ╔═╗╔╦╗╔╦╗╦═╗ ╔╦╗╔═╗╔═╗ ╦╔╗╔ ╔╦╗╔═╗╔╦╗╔═╗╦
- // ├┤ ├─┤│ ├─┤ ╠═╣ ║ ║ ╠╦╝ ║║║╣ ╠╣ ║║║║ ║║║║ ║ ║║║╣ ║
- // └─┘┴ ┴└─┘┴ ┴ ╩ ╩ ╩ ╩ ╩╚═ ═╩╝╚═╝╚ ╩╝╚╝ ╩ ╩╚═╝═╩╝╚═╝╩═╝
- // Loop over this model's defined attributes.
- _.each(ParentWLModel.attributes, function iterateAttributes(attrDef, attrName) {
- // If this attribute is SOMETHING OTHER THAN AN ASSOCIATION...
- if (!attrDef.model && !attrDef.collection) {
- // Then we just skip it.
- return;
- } // -•
- // But otherwise, we know we've got an association of some kind.
- // So we've got more work to do.
- // Look up the right-hand side value of this key in the parent record
- var valueInParentRecord = record[attrName];
- // Look up the live Waterline model referenced by this association.
- var childModelIdentity = attrDef.model || attrDef.collection;
- var ChildWLModel = models[childModelIdentity];
- // If this attribute is a singular association...
- if (attrDef.model) {
- // If `arePhysical` was specified, then use the value under this column name
- // (instead of whatever is under the attribute name)
- if (arePhysical) {
- valueInParentRecord = record[attrDef.columnName];
- }
- // If this singular association doesn't seem to be populated,
- // then simply ignore it and skip ahead.
- if (!_.isObject(valueInParentRecord) || _.isArray(valueInParentRecord) || _.isFunction(valueInParentRecord)) {
- return;
- }
- // But otherwise, it seems populated, so we'll assume it is
- // a child record and call our iteratee on it.
- var childRecord = valueInParentRecord;
- iteratee(childRecord, ChildWLModel, 2);
- // Otherwise, this attribute is a plural association...
- } else {
- // If this plural association doesn't seem to be populated,
- // then simply ignore it and skip ahead.
- if (!_.isArray(valueInParentRecord)) {
- return;
- }
- // But otherwise, it seems populated, so we'll assume it is
- // an array of child records and call our iteratee once for
- // each item in the array.
- var childRecords = valueInParentRecord;
- _.each(childRecords, function iterateChildRecords(thisChildRecord) {
- // Note that `thisChildRecord` is not guaranteed to be a dictionary!
- // (if you need this guarantee, use the `iteratee` to verify this when
- // it runs for parent records)
- iteratee(thisChildRecord, ChildWLModel, 2);
- });
- } // </ else (i.e. this is a plural assoc.) >
- }); // </ each defined attribute from model >
- }); // </ each parent record >
- // There is no return value.
- };
- /**
- * to demonstrate basic usage in the node REPL:
- */
- /* ```
- records = [{ _id: 'asdf', __pet: { _cool_id: 'asdf' }, pets: 'some crazy invalid value' }]; require('./lib/each-record-deep')(records, function(record, WLModel, depth){ console.log('\n• Ran iteratee for '+(depth===1?'parent':'child')+' record of model `'+WLModel.identity+'`:',util.inspect(record, {depth:5}),'\n'); }, false, 'user', { collections: { user: { identity: 'user', attributes: { id: { type: 'string', required: true, unique: true, columnName: '_id' }, age: { type: 'number', required: false, defaultsTo: 99 }, foo: { type: 'string', required: true }, favPet: { model: 'pet', columnName: '__pet' }, pets: { collection: 'pet' } }, primaryKey: 'id', hasSchema: true, fetchRecordsOnDestroy: true, cascadeOnDestroy: true}, pet: { identity: 'pet', attributes: { id: { type:'number', required: true, unique: true, columnName: '_cool_id' } }, primaryKey: 'id', hasSchema: true } } }); console.log('\n\nAll done.\n--\nRecords are now:\n'+util.inspect(records,{depth:5}));
- ```*/
|