Source: Resource.js

'use strict';

var request = require('superagent');
var Route = require('./Route');
var Promise = require('es6-promise').Promise;
var Collection = require('./Collection');

/**
 * Resource Factory
 *
 * @example
 * var MyResource = new Resource({url: '/myresources'});
 * // MyResource now works with the /myresources endpoint
 * MyResource.query();
 *
 * @param {Object} options
 * @param {String} options.url      root url for the resource collection
 * @param {Array}  options.idParams params that represent the resource id
 */
function ResourceFactory(options) {
  if (typeof options === 'string') {
    options = {url: options};
  }

  var route = new Route(options.url);

  /**
   * Resource
   *
   * Abstract class that provides common behavior and serialization for resources.
   *
   * @class
   * @param {Object} attrs attributes
   */
  function Resource(attrs) {
    for (var key in attrs) {
      this[key] = attrs[key];
    }
  }

  Resource.Collection = new Collection({resource: Resource});

  if (!options.idParams) {
    options.idParams = ['id'];
  }

  /**
   * Extract resource id from an object.
   *
   * By default the returns the `id` property.
   * Override to provide a different value.
   */
  Resource.getId = function(params) {
    for (var i = 0; i < options.idParams.length; i++) {
      if (params[options.idParams[i]]) {
        return params[options.idParams[i]];
      }
    }
  };

  /**
   * Delete id(s) from the params.
   *
   * By default removes the `id` property.
   */
  Resource.cleanId = function(params) {
    var cleaned = [];
    Object.keys(params).forEach(function(key) {
      if (options.idParams.indexOf(key) === -1) {
        cleaned[key] = params[key];
      }
    });
    return cleaned;
  };

  /**
   * Fetch collection.
   *
   * @example
   * var collection = Resource.query();
   * collection.then(function(data) {
   *   // data are now fetched
   * });
   *
   * @param  {Object}     params  query params
   * @return {Collection} collection and its promise
   */
  Resource.query = function(params) {
    var collection = new Resource.Collection();
    var promise = new Promise(function(resolve, reject) {
      request
        .get(route.url(params))
        .query(route.query(params))
        .accept('json')
        .end(function(response) {
          if (response.error) {
            var error = (response.body || JSON.parse(response.text)).error || response.error;
            reject({error: error});
          } else {
            var body = response.body || JSON.parse(response.text);

            collection.length = 0;
            var newCollection = new Resource.Collection();
            body.data.forEach(function(data) {
              route.params.forEach(function(param) {
                data[param] = params[param];
              });
              newCollection.push(new Resource(data));
            });
            collection.push.apply(collection, newCollection);

            if (body.pagination && body.pagination.next_page) {
              collection.next_page = body.pagination.next_page;
              newCollection.next_page = body.pagination.next_page;
            }

            if (body.pagination && body.pagination.previous_page) {
              collection.prev_page = body.pagination.previous_page;
              newCollection.prev_page = body.pagination.previous_page;
            }

            resolve(newCollection);
          }
      }.bind(this));
    }.bind(this));
    collection.then = function() { promise.then.apply(promise, arguments); };
    collection['catch'] = function() { promise['catch'].apply(promise, arguments); };
    return collection;
  };
  /**
   * @alias query
   */
  Resource.where = Resource.query;

  /**
   * Fetch resource.
   *
   * @param  {Object}  params  parameters for fetch
   * @return {Promise} resource object that will be eventually filled with data
   */
  Resource.get = function(params) {
    var id = Resource.getId(params);
    params = Resource.cleanId(params);

    return new Promise(function(resolve, reject) {
      request
        .get(route.url(params) + '/' + id)
        .query(route.query(params))
        .accept('json')
        .end(function(response) {
          if (response.error) {
            var error = (response.body || JSON.parse(response.text)).error || response.error;
            reject({error: error});
          } else {
            var body = response.body || JSON.parse(response.text);
            route.params.forEach(function(param) {
              body.data[param] = params[param];
            });
            resolve(new Resource(body.data));
          }
      }.bind(this));
    }.bind(this));
  };
  /**
   * @alias get
   */
  Resource.find = Resource.get;

  /**
   * Create a resource.
   *
   * @param  {Object}  params  resource data
   * @return {Promise} promise
   */
  Resource.create = function(params) {
    return new Promise(function(resolve, reject) {
      request
        .post(route.url(params))
        .send(route.body(params))
        .accept('json')
        .end(function(response) {
          if (response.error) {
            var error = (response.body || JSON.parse(response.text)).error || response.error;
            reject({error: error});
          } else {
            var body = response.body || JSON.parse(response.text);
            resolve(new Resource(body.data));
          }
      }.bind(this));
    }.bind(this));
  };

  /**
   * Update resource.
   *
   * @param  {Object}  params
   * @return {Promise} promise
   */
  Resource.update = function(params) {
    var id = Resource.getId(params);
    params = Resource.cleanId(params);

    return new Promise(function(resolve, reject) {
      request
        .put(route.url(params) + '/' + id)
        .send(route.body(params))
        .accept('json')
        .end(function(response) {
          if (response.error) {
            var error = (response.body || JSON.parse(response.text)).error || response.error;
            reject({error: error});
          } else {
            var body = response.body || JSON.parse(response.text);
            resolve(new Resource(body.data));
          }
      }.bind(this));
    }.bind(this));
  };

  /**
   * Url of the resource instance.
   *
   * @return {String} resource url
   */
  Resource.prototype.url = function() {
    var id = Resource.getId(this);

    if (!id) {
      throw new Error('Cannot retrieve URL of a resource that is not saved yet and does not have an ID.');
    }

    var params = {};

    route.params.forEach(function(param) {
      params[param] = this[param];
    }.bind(this));

    return route.url(params) + '/' + id;
  };

  /**
   * Save resource.
   *
   * @return {Promise} promise
   */
  Resource.prototype.save = function() {
    var id = Resource.getId();

    if (id) {
      return Resource.update(this);
    } else {
      return Resource.create(this);
    }
  };

  /**
   * Delete a resource.
   *
   * @return {Promise} promise
   */
  Resource.prototype['delete'] = function() {
    return new Promise(function(resolve, reject) {
      request
        .del(route.url(this))
        .accept('json')
        .end(function(response) {
          if (response.error) {
            var error = (response.body || JSON.parse(response.text)).error || response.error;
            reject({error: error});
          } else {
            resolve();
          }
      }.bind(this));
    }.bind(this));
  };
  /**
   * @alias delete
   */
  Resource.prototype.del = Resource.prototype['delete'];
  /**
   * @alias delete
   */
  Resource.prototype.remove = Resource.prototype['delete'];

  return Resource;
}

module.exports = ResourceFactory;