all files / lib/waterline/model/lib/defaultMethods/ toObject.js

89.47% Statements 102/114
91.43% Branches 64/70
100% Functions 12/12
91.75% Lines 89/97
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258                                    1182×   1182× 1182×     1182×     1182×     1182× 1182× 1182× 1182× 1182×         1182×     1182×   1182×                                         1182×   1182× 1030×     151×     172×   138× 138×   138× 161×   161× 161× 161× 728×   161×     138× 138×       34× 34×         34×                   34×   16×   16× 82×     16×     18×                         1182×   1182× 5772× 5616×                         1182×   1182× 1030×     151×     172×     158× 158× 158×     158× 90×     90× 68×   68× 136× 136×     68× 68×     22×       18×     16× 16×                     1182× 1182×   1182× 6958×     1327× 72× 72×       1255×     919×     1255× 158×     68×                       1182× 4773×          
/**
 * Module dependencies
 */
 
var _ = require('lodash');
var utils = require('../../../utils/helpers');
var hasOwnProperty = utils.object.hasOwnProperty;
 
/**
 * Model.toObject()
 *
 * Returns an object containing just the model values. Useful for doing
 * operations on the current values minus the instance methods.
 *
 * @param {Object} context, Waterline collection instance
 * @param {Object} proto, model prototype
 * @api public
 * @return {Object}
 */
 
var toObject = module.exports = function(context, proto) {
 
  var self = this;
 
  this.context = context;
  this.proto = proto;
 
  // Hold joins used in the query
  this.usedJoins = [];
 
  // Create an object that can hold the values to be returned
  this.object = {};
 
  // Run methods to add and modify values to the above object
  this.addAssociations();
  this.addProperties();
  this.makeObject();
  this.filterJoins();
  this.filterFunctions();
 
  // Ok now we want to create a POJO that can be serialized for use in a response.
  // This is after all usually called in a toJSON method so lets make sure its all
  // good in there. This could be faster and safer I recon.
  try {
 
    // Stringify/parse the object
    var _obj = JSON.parse(JSON.stringify(this.object));
 
    return _obj;
 
  // Return a nicer error message than just throwing the json parse message
  } catch (e) {
    var err = new Error();
    err.message = 'There was an error turning the model into an object.';
    err.data = self.object;
    throw err;
  }
};
 
 
/**
 * Add Association Keys
 *
 * If a showJoins flag is active, add all association keys.
 *
 * @param {Object} keys
 * @api private
 */
 
toObject.prototype.addAssociations = function() {
  var self = this;
 
  if (!this.proto._properties) return;
  if (!this.proto._properties.showJoins) return;
 
  // Copy prototype over for attributes
  for (var association in this.proto.associations) {
 
    // Handle hasMany attributes
    if (hasOwnProperty(this.proto.associations[association], 'value')) {
 
      var records = [];
      var values = this.proto.associations[association].value;
 
      values.forEach(function(record) {
        Iif (typeof record !== 'object') return;
        // Since `typeof null` === `"object"`, we should also check for that case:
        Iif (record === null) return;
        var item = Object.create(record.__proto__);
        Object.keys(record).forEach(function(key) {
          item[key] = _.cloneDeep(record[key]);
        });
        records.push(item);
      });
 
      this.object[association] = records;
      continue;
    }
 
    // Handle belongsTo attributes
    var record = this.proto[association];
    var item;
 
    // Check if the association foreign key is a date. If so set the object's
    // association and continue. Manual check here is needed because _.isObject
    // matches dates and you will end up with a loop that never exits.
    Iif (_.isDate(record)) {
 
      item = new Date(record);
      _.extend(item.__proto__ , record.__proto__);
 
      this.object[association] = item;
    }
 
    // Is the record is a populated object, create a new object from it.
    // _.isObject() does not match null, so we're good here.
    else if (_.isObject(record) && !Array.isArray(record)) {
 
      item = Object.create(record.__proto__);
 
      Object.keys(record).forEach(function(key) {
        item[key] = record[key];
      });
 
      this.object[association] = item;
    }
 
    else if (!_.isUndefined(record)) {
      this.object[association] = record;
    }
  }
};
 
/**
 * Add Properties
 *
 * Copies over non-association attributes to the newly created object.
 *
 * @api private
 */
 
toObject.prototype.addProperties = function() {
  var self = this;
 
  Object.keys(this.proto).forEach(function(key) {
    if (hasOwnProperty(self.object, key)) return;
    self.object[key] = self.proto[key];
  });
 
};
 
/**
 * Make Object
 *
 * Runs toJSON on all associated values
 *
 * @api private
 */
 
toObject.prototype.makeObject = function() {
  var self = this;
 
  if (!this.proto._properties) return;
  if (!this.proto._properties.showJoins) return;
 
  // Handle Joins
  Object.keys(this.proto.associations).forEach(function(association) {
 
    // Don't run toJSON on records that were not populated
    if (!self.proto._properties || !self.proto._properties.joins) return;
 
    // Build up a join key name based on the attribute's model/collection name
    var joinsName = association;
    if (self.context._attributes[association].model) joinsName = self.context._attributes[association].model.toLowerCase();
    if (self.context._attributes[association].collection) joinsName = self.context._attributes[association].collection.toLowerCase();
 
    // Check if the join was used
    if (self.proto._properties.joins.indexOf(joinsName) < 0 && self.proto._properties.joins.indexOf(association) < 0) return;
    self.usedJoins.push(association);
 
    // Call toJSON on each associated record
    if (Array.isArray(self.object[association])) {
      var records = [];
 
      self.object[association].forEach(function(item) {
        Iif (!hasOwnProperty(item.__proto__, 'toJSON')) return;
        records.push(item.toJSON());
      });
 
      self.object[association] = records;
      return;
    }
 
    if (!self.object[association]) return;
 
    // Association was null or not valid
    // (don't try to `hasOwnProperty` it so we don't throw)
    if (typeof self.object[association] !== 'object') {
      self.object[association] = self.object[association];
      return;
    }
 
    Iif (!hasOwnProperty(self.object[association].__proto__, 'toJSON')) return;
    self.object[association] = self.object[association].toJSON();
  });
 
};
 
/**
 * Remove Non-Joined Associations
 *
 * @api private
 */
 
toObject.prototype.filterJoins = function() {
  var attributes = this.context._attributes;
  var properties = this.proto._properties;
 
  for (var attribute in attributes) {
    if (!hasOwnProperty(attributes[attribute], 'model') && !hasOwnProperty(attributes[attribute], 'collection')) continue;
 
    // If no properties and a collection attribute, delete the association and return
    if (!properties && hasOwnProperty(attributes[attribute], 'collection')) {
      delete this.object[attribute];
      continue;
    }
 
    // If showJoins is false remove the association object
    if (properties && !properties.showJoins) {
 
      // Don't delete belongs to keys
      if (!attributes[attribute].model) delete this.object[attribute];
    }
 
    if (properties && properties.joins) {
      if (this.usedJoins.indexOf(attribute) < 0) {
 
        // Don't delete belongs to keys
        if (!attributes[attribute].model) delete this.object[attribute];
      }
    }
  }
};
 
/**
 * Filter Functions
 *
 * @api private
 */
 
toObject.prototype.filterFunctions = function() {
  for (var key in this.object) {
    Iif (typeof this.object[key] === 'function') {
      delete this.object[key];
    }
  }
};