makepanic/ember-data-link-traversal

View on GitHub
addon/traversal.js

Summary

Maintainability
A
1 hr
Test Coverage
import Ember from 'ember';
import DS from 'ember-data';

let {Model} = DS;
let {RSVP} = Ember;

// const when https://github.com/jshint/jshint/issues/2430 is in ember-cli-qunit
var TRAVERSAL_TEMPLATE_OPTIONS = '__traversal-template-options',//Symbol('traversal-template-options'),
  TRAVERSAL_QUERY_LINK = '__traversal-query-link', //Symbol('traversal-query-link'),
  TRAVERSAL_META = '__traversal-meta', //Symbol('traversal-meta'),
  TRAVERSAL_ROOT = '__traversal-root'; //Symbol('traversal-root');

// let when https://github.com/jshint/jshint/issues/2430 is in ember-cli-qunit
var rootModel = 'entrypoint',
  extractRecordLinks;

const DEFAULT_CONFIGURATION = {
  rootModel: 'entrypoint',
  extractRecordLinks(record) {
    return record.get('data.links') || record.get('meta.links');
  }
};

/**
 * Function to configure various fields
 * @param configuration
 */
function configure(configuration = {}) {
  let merged = Ember.$.extend({}, DEFAULT_CONFIGURATION, configuration);
  rootModel = merged.rootModel;
  extractRecordLinks = merged.extractRecordLinks;
}

configure();

/**
 * Check if a given value is a DS.Model instance
 * @param {*} value
 * @returns {boolean} true if value is a DS.Model
 */
function isDSModel(value) {
  return value !== undefined && value instanceof Model ||
    (value && value.content && value.content instanceof Model);
}

function callQueryMethod(store, record, path, templateParams) {
  const relationship = record.relationshipFor(path),
    modelClass = store.modelFor(relationship.type),
    queryMethod = relationship.kind === 'belongsTo' ? 'queryRecord' : 'query';

  return store[queryMethod](modelClass.modelName, {
    modelClass: modelClass,
    [TRAVERSAL_QUERY_LINK]: extractRecordLinks(record)[path],
    templateParams: templateParams
  });
}

/**
 * Follow a records links by a given path.
 * Provide a templateParams object to expand possible uri templates.
 * @param {DS.Store} store
 * @param {DS.Record} record
 * @param {Array.<String>} paths
 * @param templateParams
 * @returns {String|Function|{}|void|Promise.<*>|*}
 */
function followFromRecord(store, record, paths, templateParams) {
  let promise = RSVP.resolve(record);

  paths.forEach(path => {
    promise = promise.then(record => {
      if (templateParams) {
        record.set(TRAVERSAL_TEMPLATE_OPTIONS, templateParams);
      }
      return callQueryMethod(store, record, path, templateParams);
    });
  });

  return promise;
}

//Follows a given path to a resource using
function followFromRoot(store, paths, templateParams) {
  return store.findRecord(rootModel, 1).then(record => {
    return followFromRecord(store, record, paths, templateParams);
  });
}

function follow(store, first, ...paths) {
  let templateParams,
    isModel = isDSModel(first);

  if (paths.length > 0 && typeof paths[paths.length - 1] !== 'string') {
    templateParams = paths.pop();
  }

  return isModel ?
    followFromRecord(store, first, paths, templateParams) :
    followFromRoot(store, [first].concat(paths), templateParams);
}

function followUrl(store, modelClass, url, templateParams = {}) {
  return store.query(modelClass.modelName, {
    modelClass: modelClass,
    [TRAVERSAL_QUERY_LINK]: url,
    templateParams: templateParams
  });
}

/**
 * Function to create a record by following a chain of link relations and POSTing the given record
 * @param {DS.Store} store Ember data store
 * @param {String|DS.Model} first First link to follow or record to start the link traversal from
 * @param {Array.<String>} paths
 * @returns {void|Promise|Promise.<T>} Promise that resolves after the record is created.
 */
function save(store, first, ...paths) {
  let newRecord = paths.pop(),
    templateParams;

  if (paths.length > 0 && typeof paths[paths.length - 1] !== 'string') {
    templateParams = paths.pop();
  }

  return follow(...arguments).then(record => {
    newRecord.set(TRAVERSAL_META, {
      [TRAVERSAL_QUERY_LINK]: extractRecordLinks(record).self,
      templateParams: templateParams
    });

    return newRecord.save();
  });
}

export {
  save,
  follow,
  followUrl,
  configure,
  extractRecordLinks,
  rootModel,
  TRAVERSAL_ROOT,
  TRAVERSAL_META,
  TRAVERSAL_TEMPLATE_OPTIONS,
  TRAVERSAL_QUERY_LINK
};