Source: lib/resource/list.js

/*jshint laxcomma: true, smarttabs: true, node:true, esnext:true */
'use strict';
/**
 * Mixin class providing functionality for listing endpoints
 * @module tastypie/lib/resource/list
 * @author Eric Satterwhite
 * @since 1.0.1
 * @requires debug
 * @requires class
 * @requires class/options
 * @requires boom
 * @requires async
 * @requires mout/collection/forEach
 * @requires tastypie/lib/http
 * @requires tastypie/lib/class
 **/
var Boom         = require('boom')
  , EventEmitter = require('events').EventEmitter
  , async        = require('async')
  , isString     = require('mout/lang/isString')
  , each         = require('mout/collection/forEach')
  , debug        = require('debug')("tastypie:resources:list")
  , Class        = require('../class')
  , http         = require('../http')
  , {annotate}   = require('../utility')
  , List
  ;


/**
 * @mixin
 * @alias module:tastypie/lib/resource/list
 * @extends EventEmitter
 */
List = new Class({
  inherits: EventEmitter
  
  /**
   * Internal method used during the `post_list` method
   * @protected
   * @method module:tastypie/lib/resource/list#create_object
   * @param {Bundle} bundle A bundle representing the current request
   * @param {module:tastypie/lib/resource~Nodeback} callback a callback function to call when the operation is complete
   **/
  , create_object: function create_object( bundle, callback ){
    var e = Boom.notImplemented( "create_object is not implemented" );
    e.req = bundle.req;
    e.res = bundle.res;

    callback( e );
  }

  /**
   * disaptches a list request operating on a collection of objects.
   * If any additional check / balances or processing needs to occur before a request is actually
   * dispatched to the handler, this would be a good place to do that.
   * @method module:tastypie/lib/resource/list#dispatch_list
   * @param {Request} request A hapijs request object
   * @param {Function} reply A hapis reply function
   **/
  , dispatch_list: function list( req, reply ){
    return this.dispatch( 'list', this.bundle( req, reply ) );
  }

  /**
   * Top level function used to handle get requests for listing endpoints. It handles paging and serialization
   * @method module:tastypie/lib/resource/list#get_list
   * @param {Bundle} bundle A bundle representing the current request
   **/
  , get_list: function get_list( bundle ){
    this.get_objects( bundle, ( e, objects ) => {
      var _obs  = objects
        , query = bundle.req.query
        , uri = bundle.req.path
        , page  = this.options.paginator
        , offset = query.offset
        , limit = query.limit
        , prop  = this.options.collection
        , max   = this.options.max
        , to_serialize
        ;
      
      if( e ) return this.emit('error', annotate(e, bundle) );
      _obs = objects || '[]';

      if( isString( _obs ) || Buffer.isBuffer( _obs ) ){
        _obs = JSON.parse(_obs)
      }
       
      _obs = this.sort( _obs );
      to_serialize = page(uri, limit, query.offset, _obs, _obs.length, max, prop);
      
      async.map( to_serialize[ prop ],( item, done ) => {
        this.full_dehydrate( item, bundle, done );
      }, ( err, results ) => {
        
        if( err ) return this.emit('error', annotate(err, bundle) );

        to_serialize[ prop ] = results;
        bundle.data = to_serialize;
        page = to_serialize = null;
        this.respond( bundle );
      });

    });
  }

  /**
   * Internal method used to retrieve a full list of objects for a resource
   * @method module:tastypie/lib/resource/list#get_objects
   * @param {module:tastypie/lib/resource~Bundle} bundle
   * @param {module:tastypie/lib/resource~Nodeback} callback callback function to call when data retrieval is
   **/
  , get_objects: function get_objects( bundle, callback ){
    var e = Boom.notImplemented( "get_objects is not implemented" );
    e.req = bundle.req;
    e.res = bundle.res;
    callback( e );
  }

  /**
   * High level function called in response to an OPTIONS request. Returns with an Allow header and empty body
   * @method module:tastypie/lib/resource/detail#options_detail
   * @param {module:tastypie/lib/resource~Bundle} bundle An object represneting the current `OPTIONS` request
   **/
  , options_list: function options_list( bundle ){
    bundle.data = null;
    this.respond( bundle, null, null, ( err, reply ) => {
      let methods = [];
      each( this.options.allowed.list,function( value, key ){
        if( value ){
          methods.push( key.toUpperCase() );
        }
      });
      reply.header('Allow',methods.join(','));
    });
  }
  /**
   * Top level method used to handle post request to listing endpoints. Used to create new instances
   * of the associated resource
   * @method module:tastypie/lib/resource/list#post_list
   * @param {Bundle} bundle An object represneting the current `POST` request
   **/
  , post_list: function post_list( bundle ){
    var response = http.created
      , format = this.format( bundle, this.options.serializer.types )
      , that = this
      ;

    this.deserialize( bundle.req.payload, format, function( err, data ){
      bundle = that.bundle(bundle.req, bundle.res, data );

      // create_object must save the object
      that.create_object( bundle, function( err, bndl ){
        if( err ){
          err.res = bundle.res;
          err.req = bundle.req;
          return that.emit( 'error', err );
        }

        if( !that.options.returnData ){
          bundle.data = null;
          return that.respond( bndl, http.noContent );
        } 

        that.full_dehydrate( bndl.object, bndl, function( err, data ){
          bndl.data = data;
          return that.respond( bndl, response );
        });
      });
    });
  }
});

module.exports = List;