Show:
import Mixin from '@ember/object/mixin'
import { typeOf, isNone } from '@ember/utils';
import { assert } from '@ember/debug';
import { get } from '@ember/object';
import { isArray } from '@ember/array';
import generateUniqueId from '../utils/generate-unique-id';

/**
  Mixin for base model to support creating record by prototype.

  @module ember-flexberry-data
  @class CopyableMixin
  @extends Ember.Mixin
*/
export default Mixin.create({
  /**
    The default projection name to be used when creating record by prototype.

    @property prototypeProjection
    @type String
   */
  prototypeProjection: undefined,

  /**
    Creates a new record using base model instance with the specified
    or default projection as prototype.

    @method copy
    @param {String} prototypeProjection Optional projection name to be used when creating record by prototype.
    @return {Promise} promise
  */
  copy(prototypeProjection) {
    if (isNone(prototypeProjection)) {
      prototypeProjection = this.get('prototypeProjection');
    }

    assert('Prototype projection is undefined.', prototypeProjection);

    let argumentType = typeOf(prototypeProjection);
    assert(`Wrong type of \`prototypeProjection\` argument: actual type is ${argumentType}, but string is expected.`,
    argumentType === 'string');

    assert(`Projection with \`${prototypeProjection}\` name is not found in the model.`,
    !isNone(this.get('constructor.projections')[prototypeProjection]));

    let store = this.get('store');
    let modelName = this.get('constructor.modelName');

    let promise = store.findRecord(modelName, this.id, { reload: true, projection: prototypeProjection }).then((prototype) => {
      return prototype._getCopy();
    });

    return promise;
  },

  _getCopy(aggregatorMeta) {
    let store = this.get('store');
    let modelName = this.get('constructor.modelName');

    let record = store.createRecord(modelName, { id: generateUniqueId() });

    this.eachAttribute(name => {
      let value = this.get(name);
      record.set(name, value);
    });

    this.eachRelationship((name, meta) => {
      let key = meta.key;

      if (meta.kind === 'belongsTo') {
        // Skip copying the aggregator value for the nested detail.
        let inverse = get(meta, 'options.inverse');
        if (aggregatorMeta && aggregatorMeta.type === meta.type && aggregatorMeta.key === inverse) {
          return;
        }

        let value = this.get(key);
        record.set(key, value);

        return;
      }

      let values = this.get(key);
      if (isArray(values)) {
        values.forEach(prototype => {
          let m = { key: key, type: modelName };
          let detail = prototype._getCopy(m);
          record.get(key).pushObject(detail);
        });
      }
    });

    return record;
  },
});