Source: models/dataobject.js

import stampit from 'stampit';
import {Meta, Model} from './base';
import Promise from 'bluebird';
import _ from 'lodash';
import QuerySet, {Filter} from '../querySet';

const DataObjectQuerySet = stampit().compose(QuerySet, Filter).methods({
  /**
    * Orders DataObject by field.

    * @memberOf QuerySet
    * @instance

    * @param {String} field
    * @returns {QuerySet}

    * @example {@lang javascript}
    * DataObject.please().list({ instanceName: 'test-instace', className: 'test-class' }).orderBy('author').then(function(dataobjects) {});
    * DataObject.please().list({ instanceName: 'test-instace', className: 'test-class' }).orderBy('-author').then(function(dataobjects) {});

    */
  orderBy(field) {
    this.query['order_by'] = field;
    return this;
  },
  /**
  * Adds an array to an array field.

  * @memberOf QuerySet
  * @instance

  * @param {Object} properties lookup properties used for path resolving
  * @param {Object} field to add to.
  * @returns {QuerySet}

  * @example {@lang javascript}
  * DataObject.please().add({instanceName: 'my-instance', className: 'my-class', id: 1}, {array_field: [1,2]})

  */
  add(properties = {}, object = {}) {
    const payload = {};
    payload[_.keys(object)[0]] = { _add: object[_.keys(object)[0]] };
    this.properties = _.assign({}, this.properties, properties);
    this.payload = JSON.stringify(payload);

    this.method = 'PATCH';
    this.endpoint = 'detail';
    return this;
  },
  /**
  * Adds an array to an array field without duplicate values.

  * @memberOf QuerySet
  * @instance

  * @param {Object} properties lookup properties used for path resolving
  * @param {Object} field to add to.
  * @returns {QuerySet}

  * @example {@lang javascript}
  * DataObject.please().add({instanceName: 'my-instance', className: 'my-class', id: 1}, {array_field: [1,2]})

  */
  addUnique(properties = {}, object = {}) {
    const payload = {};
    payload[_.keys(object)[0]] = { _addunique: object[_.keys(object)[0]] };
    this.properties = _.assign({}, this.properties, properties);
    this.payload = JSON.stringify(payload);

    this.method = 'PATCH';
    this.endpoint = 'detail';
    return this;
  },
  /**
    * Filters DataObjects using _is.

    * @memberOf QuerySet
    * @instance

    * @param {String} field name
    * @param {Object} filters
    * @returns {QuerySet}

    * @example {@lang javascript}
    * DataObject.please().list({instanceName: 'my-instance', className: 'books'}).is('authors', { name: { _eq: 'Stephen King'}})

    */
  is(field, object = {}) {
    const query = {};
    query[field] = { _is: object};
    return this.filter(query);
  },
  /**
    * Filters DataObjects using _contains.

    * @memberOf QuerySet
    * @instance

    * @param {String} field name
    * @param {Array} array of ids
    * @returns {QuerySet}

    * @example {@lang javascript}
    * DataObject.please().list({instanceName: 'my-instance', className: 'books'}).contains('authors', [1, 2, 3])

    */
  contains(field, array = []) {
    const query = {};
    query[field] = { _contains: array};
    return this.filter(query);
  },
  /**
  * Subtracts an array from an array field.

  * @memberOf QuerySet
  * @instance

  * @param {Object} properties lookup properties used for path resolving
  * @param {Object} field to subtract from.
  * @returns {QuerySet}

  * @example {@lang javascript}
  * DataObject.please().remove({instanceName: 'my-instance', className: 'my-class', id: 1}, {array_field: [1,2]})

  */
  remove(properties = {}, object = {}) {
    const payload = {};
    payload[_.keys(object)[0]] = { _remove: object[_.keys(object)[0]] };
    this.properties = _.assign({}, this.properties, properties);
    this.payload = JSON.stringify(payload);

    this.method = 'PATCH';
    this.endpoint = 'detail';
    return this;
  },
  /**
  * Increments single object based on provided arguments

  * @memberOf QuerySet
  * @instance

  * @param {Object} properties lookup properties used for path resolving
  * @param {Object} field to increment.
  * @returns {QuerySet}

  * @example {@lang javascript}
  * DataObject.please().increment({instanceName: 'my-instance', className: 'my-class', id: 1}, {views: 1})

  */
  increment(properties = {}, object = {}) {
    const payload = {};
    payload[_.keys(object)[0]] = { _increment: object[_.keys(object)[0]] };
    this.properties = _.assign({}, this.properties, properties);
    this.payload = JSON.stringify(payload);

    this.method = 'PATCH';
    this.endpoint = 'detail';
    return this;
  },
  /**
    * Filters dataobjects by a geopoint field.

    * @memberOf QuerySet
    * @instance

    * @param {Object} coordinates
    * @returns {QuerySet}

    * @example {@lang javascript}
    * DataObject.please().list({ instanceName: 'test-instace', className: 'test-class' }).near({ geopoint_field_name: { latitude: POINT_LATITUDE, longitude: POINT_LONGITUDE }}).then(function(dataobjects) {});

    * @example {@lang javascript}
    * DataObject.please().list({ instanceName: 'test-instace', className: 'test-class' }).near({ geopoint_field_name: { latitude: POINT_LATITUDE, longitude: POINT_LONGITUDE, distance_in_kilometers: DISTANCE_IN_KILOMETERS }}).then(function(dataobjects) {});

    * @example {@lang javascript}
    * DataObject.please().list({ instanceName: 'test-instace', className: 'test-class' }).near({ geopoint_field_name: { latitude: POINT_LATITUDE, longitude: POINT_LONGITUDE, distance_in_miles: DISTANCE_IN_MILES }}).then(function(dataobjects) {});

    */
  near(object = {}) {
    const query = {};
    query[_.keys(object)[0]] = { _near: object[_.keys(object)[0]]};
    return this.filter(query);
  },
  /**
    * Returns DataObject count.

    * @memberOf QuerySet
    * @instance

    * @returns {QuerySet}

    * @example {@lang javascript}
    * DataObject.please().list({ instanceName: 'test-instace', className: 'test-class' }).count().then(function(response) {});

    */
  count() {
    this.query['include_count'] = true;
    this.raw();
    return this;
  }

});

const DataObjectMeta = Meta({
  name: 'dataobject',
  pluralName: 'dataobjects',
  endpoints: {
    'detail': {
      'methods': ['delete', 'patch', 'post', 'get'],
      'path': '/v1.1/instances/{instanceName}/classes/{className}/objects/{id}/'
    },
    'list': {
      'methods': ['post', 'get'],
      'path': '/v1.1/instances/{instanceName}/classes/{className}/objects/'
    }
  }
});

const DataobjectConstraints = {
  instanceName: {
    presence: true,
    length: {
      minimum: 5
    }
  },
  className: {
    presence: true,
    string: true
  },
  owner: {
    numericality: true
  },
  owner_permissions: {
    inclusion: ['none', 'read', 'write', 'full']
  },
  group: {
    numericality: true
  },
  group_permissions: {
    inclusion: ['none', 'read', 'write', 'full']
  },
  other_permissions: {
    inclusion: ['none', 'read', 'write', 'full']
  },
  channel: {
    string: true
  },
  channel_room: {
    string: true
  }
};

/**
 * OO wrapper around instance data objects {@link http://docs.syncano.com/v4.0/docs/view-data-objects endpoint}.
 * This model is special because each instance will be **dynamically populated** with fields defined in related {@link Class} schema attribute.
 * @constructor
 * @type {DataObject}

 * @property {Number} id
 * @property {String} instanceName
 * @property {String} className
 * @property {Number} revision
 * @property {Number} [owner = null]
 * @property {String} [owner_permissions = null]
 * @property {Number} [group = null]
 * @property {String} [group_permissions = null]
 * @property {String} [other_permissions = null]
 * @property {String} [channel = null]
 * @property {String} [channel_room = null]

 * @property {String} [description = null]
 * @property {String} [links = {}]
 * @property {Date} [created_at = null]
 * @property {Date} [updated_at = null]
 */
const DataObject = stampit()
  .compose(Model)
  .setMeta(DataObjectMeta)
  .methods({
    /**
    * Gets related objects via relation field name.

    * @memberOf QuerySet
    * @instance

    * @param {String} field name.
    * @returns {QuerySet}

    * @example {@lang javascript}
    * Object.getRelatedObjects('authors');

    */
    getRelatedObjects(field) {
      if(!_.has(this, field)) return Promise.reject(new Error(`The ${field} field does not exist.`));
      if(!_.has(this[field], 'value') || !_.isArray(this[field].value)) return Promise.reject(new Error(`The ${field} is not a relation.`));

      const {DataObject} =  this.getConfig();

      return DataObject.please().list({instanceName: this.instanceName, className: this[field].target }).filter({ id: { _in: this[field].value }});
    },
    /**
    * Increments single object field based on provided arguments.

    * @memberOf QuerySet
    * @instance

    * @param {String} field name.
    * @param {Number} value to increment the field by,
    * @returns {QuerySet}

    * @example {@lang javascript}
    * Object.increment('views', 1);

    */
    increment(field, by) {
      if(!_.isNumber(this[field])) return Promise.reject(new Error(`The ${field} is not numeric.`));
      if(!_.isNumber(by)) return Promise.reject(new Error('The provided value is not numeric.'));

      this[field] += _.add(this[field], by);

      return this.save();
    },
    /**
    * Adds an array to an array field.

    * @memberOf QuerySet
    * @instance

    * @param {String} field name.
    * @param {Array} array to add to the field.
    * @returns {QuerySet}

    * @example {@lang javascript}
    * Object.add('authors', [1,2,3]);

    */
    add(field, array) {
      if((_.has(this[field], 'value') && !_.isArray(this[field].value)) || (!_.has(this[field], 'value') && !_.isArray(this[field]))) return Promise.reject(new Error(`The ${field} is not an array.`));
      if(!_.isArray(array)) return Promise.reject(new Error('The provided value is not an array.'));

      this[field] = _.has(this[field], 'value') ? _.concat(this[field].value, array) :  _.concat(this[field], array);

      return this.save();
    },
    /**
    * Adds an array to an array field without duplicate values.

    * @memberOf QuerySet
    * @instance

    * @param {String} field name.
    * @param {Array} array to add to the field.
    * @returns {QuerySet}

    * @example {@lang javascript}
    * Object.add('authors', [1,2,3]);

    */
    addUnique(field, array) {
      if(!_.isArray(this[field])) return Promise.reject(new Error(`The ${field} is not an array.`));
      if(!_.isArray(array)) return Promise.reject(new Error('The provided value is not an array.'));

      this[field] = _.union(this[field], array);

      return this.save();
    },
    /**
    * Subtracts an array from an array field.

    * @memberOf QuerySet
    * @instance

    * @param {String} field name.
    * @param {Array} array to subtract from the field.
    * @returns {QuerySet}

    * @example {@lang javascript}
    * Object.remove('authors', [1]);

    */
    remove(field, array) {
      if((_.has(this[field], 'value') && !_.isArray(this[field].value)) || (!_.has(this[field], 'value') && !_.isArray(this[field]))) return Promise.reject(new Error(`The ${field} is not an array.`));
      if(!_.isArray(array)) return Promise.reject(new Error('The provided value is not an array.'));

      this[field] = _.has(this[field], 'value') ? _.difference(this[field].value, array) : _.difference(this[field], array);

      return this.save();
    }
  })
  .setQuerySet(DataObjectQuerySet)
  .setConstraints(DataobjectConstraints);

export default DataObject;