jmdobry/reheat

View on GitHub
lib/model/prototype/destroy.js

Summary

Maintainability
C
1 day
Test Coverage
var errorPrefix = 'Model#destroy([options][, cb]): ';

module.exports = function (Promise, utils, errors, models) {
  /**
   * @doc method
   * @id Model.instance_methods:destroy
   * @name destroy
   * @description
   * Destroy this instance. If the Model for this instance was configured with `softDelete: false`, then
   * the row specified by this instance's primary key will be removed from the database. If the Model for this instance
   * was configured with `softDelete: true`, then the row specified by the instance's primary key will be
   * updated with a `deleted` property set to the UTC datetime (of the database) at which the operation occurs.
   *
   * ## Signature:
   * ```js
   * Model#destroy([options][, cb])
   * ```
   *
   * ## Examples:
   *
   * ### Promise-style:
   * ```js
   *  post.destroy().then(function (post) {
     *      res.send(204, post.get(post.constructor.idAttribute));
     *  })
   *  .error(function (err) {
     *      res.send(500, err.message);
     *  });
   * ```
   *
   * ### Node-style:
   * ```js
   *  post.destroy(function (err, post) {
     *      if (err) {
     *          res.send(500, err.message);
     *      } else {
     *          res.send(204, post.get(post.constructor.idAttribute));
     *      }
     *  });
   * ```
   *
   * ## Throws/Rejects with:
   *
   * - `{IllegalArgumentError}`
   * - `{UnhandledError}`
   *
   * @param {object=} options Optional configuration options. Properties:
   *
   * - `{boolean=false}` - `deepDestroy` - If `true`, call `destroy()` on any hasOne or hasMany relations currently
   * loaded into this model instance.
   *
   * @param {function=} cb Optional callback function for Node-style usage. Signature: `cb(err, instance)`. Arguments:
   *
   * - `{IllegalArgumentError|UnhandledError}` - `err` - `null` if no error occurs.
   * - `{object}` - `instance` - If no error occurs, a reference to the instance on which `destroy([options][, cb])` was called.
   * @returns {Promise} Promise.
   */
  function destroy(options, cb) {
    var Model = this.constructor;
    var tasks = [];
    if (utils.isFunction(options)) {
      cb = options;
      options = {};
    }
    options = options || {};
    if (cb && !utils.isFunction(cb)) {
      throw new errors.IllegalArgumentError(errorPrefix + 'cb: Must be a function!', { actual: typeof cb, expected: 'function' });
    }
    function checkRelation(relation, modelName) {
      if (!models[modelName]) {
        throw new errors.RuntimeError(errorPrefix + Model.name + ' Model defined hasOne or hasMany relationship to nonexistent ' + modelName + ' Model!');
      }

      if (this.get(relation.localField)) {
        tasks.push(this.get(relation.localField).destroy());
      }
    }

    return Promise.resolve().bind(this)
      .then(function () {
        if (!utils.isObject(options)) {
          throw new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { actual: typeof options, expected: 'object' });
        }

        if (options.deepDestroy) {
          if (Model.relations.hasMany) {
            utils.forOwn(Model.relations.hasMany, checkRelation, this);
          }

          if (Model.relations.hasOne) {
            utils.forOwn(Model.relations.hasOne, checkRelation, this);
          }
        }

        if (tasks.length) {
          return Promise.all(tasks);
        }
      })
      .then(function () {
        if (!this.isNew()) {
          return Promise.promisify(this.beforeDestroy, this)().bind(this).return(Model);
        } else {
          return Model;
        }
      })
      .then(function (Model) {
        if (Model.tableReady && Model.tableReady !== true) {
          return Model.tableReady.return(Model);
        } else {
          return Model;
        }
      })
      .then(function (Model) {
        if (!this.isNew()) {
          if (Model.softDelete) {
            utils.deepMixIn(this.attributes, { deleted: Model.r.now() });
            if (Model.timestamps) {
              utils.deepMixIn(this.attributes, { updated: Model.r.now() });
            }
            return Model.connection.run(Model.r.table(Model.tableName).get(this.get(Model.idAttribute) || '').update(this.attributes, { returnChanges: true }), options);
          } else {
            return Model.connection.run(Model.r.table(Model.tableName).get(this.get(Model.idAttribute) || '').delete({ returnChanges: true }), options);
          }
        }
      })
      .then(function (cursor) {
        if (!this.isNew()) {
          if (cursor.errors !== 0) {
            throw new Error(cursor.first_error);
          } else {
            if (Model.softDelete) {
              utils.deepMixIn(this.attributes, utils.merge({}, cursor.changes[0].new_val));
            } else {
              utils.unset(this.attributes, Model.idAttribute);
            }
            this.previousAttributes = utils.merge({}, cursor.changes[0].old_val);
            this.meta = utils.merge({}, cursor);
            return Promise.promisify(this.afterDestroy, this)(this).bind(this);
          }
        } else {
          return this;
        }
      }).finally(function () {
        tasks = null;
      }).nodeify(cb);
  }

  return destroy;
};