<commit_before>require ('sugar');
var fun = require('funargs');

// NOTE: Avoiding subclassing Array for now:
//  - http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/

// -----------------------
//  Decorator
// --------------------

function Collection () {
  var args = fun(arguments);
  var collection = Array.isArray(args[0]) ? args[0] : args;

  Collection.methods.klass.each(Collection.proxy(collection));
  Collection.methods.instance.each(Collection.proxy(collection));

  return collection;
}

Collection.proxy = function(collection) {
  return function(method) {
    if (method.getter || method.setter) {

      if (method.getter) {
        collection.__defineGetter__(method.name, Collection.map(method));
      }

      if (method.setter) {
        collection.__defineSetter__(method.name, Collection.map(method));
      }

    } else {
      collection[method.name] = Collection.map(method);
    }
  };
};

Collection.map = function(method) {
  return function() {
    var self = this, args = fun(arguments), callback;

    var returned = [];
    var returned_async = [];
    var returned_async_count = 0;

    var done = function() {
      var _args = fun(arguments);

      returned_async_count += 1;

      _args.each(function(v, i) {
        returned_async[i] = returned_async[i] || [];
        returned_async[i].push(v);
      });

      if ((returned_async_count === self.length) && callback) {
        callback.apply(self, returned_async);
      }
    };

    if (!method.each && args.functions().length) {
      callback = args.functions()[0];
      args[args.indexOf(callback)] = done;
    }

    self.each(function(model) {
      var _return;

      if (method.getter || method.setter) {
        if (method.setter && args.length > 0) {
          _return = (model[method.name] = args[0]);
        } else if (method.getter) {
          _return = model[method.name];
        }
      } else {
        if (model[method.name]) {
          _return = model[method.name].apply(model, args);
        }
      }

      returned.push(_return);
    });

    return returned;
  }
};

Collection.methods = {
  klass: [
    {name: 'name', getter: true},
    {name: 'new'},
    {name: 'create'},
    {name: 'set'},
    {name: 'get'},
    {name: 'del'},
    {name: 'exists'},
    {name: 'end'},
    {name: 'validate'},
    {name: 'diff'},
    {name: 'on', each: true},
    {name: 'off', each: true},
    {name: 'emit'}
  ],
  instance: [
    {name: 'name', getter: true},
    {name: 'id', getter: true, setter: true},
    {name: 'type', getter: true},
    {name: 'persisted', getter: true},
    {name: 'unpersisted', getter: true},
    {name: 'changes', getter: true},
    {name: 'changed', getter: true},
    {name: 'valid', getter: true},
    {name: 'invalid', getter: true},
    {name: 'storage', getter: true, setter: true},
    {name: 'validator', getter: true, setter: true},
    {name: 'differ', getter: true, setter: true},
    {name: 'attributes', getter: true, setter: true}, // NOTE: Document([post, article]).attributes.title = 'A' won't work naturally - fix with nested-getters-hack on attributes?
    {name: 'persisted_attributes', getter: true, setter: true},
    {name: 'unchanged', getter: true},
    {name: 'errors', getter: true},
    {name: 'get'},
    {name: 'set'},
    {name: 'attr'},
    {name: 'reset'},
    {name: 'clear'},
    {name: 'toJSON'},
    {name: 'toString'},
    {name: 'valueOf'},
    // {name: 'inspect'}, // RESERVED: Cannot override this in Node.js; specs pass but when running in REPL this breaks `[].inspect`.
    {name: 'clone'},
    {name: 'destroy'},
    {name: 'save'},
    {name: 'fetch'},
    {name: 'on', each: true},
    {name: 'off', each: true},
    {name: 'emit'}
  ]
};

// -----------------------
//  Export
// --------------------

module.exports = Collection;
<commit_msg>[enhancement]: More reliable Collection proxying, fixes class vs. instance methods issues.<commit_after>require ('sugar');
var fun = require('funargs');

var Model = require('./model');

// NOTE: Avoiding subclassing Array for now:
//  - http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/

// -----------------------
//  Decorator
// --------------------

function Collection () {
  var args = fun(arguments);
  var collection = Array.isArray(args[0]) ? args[0] : args;

  var first_is_instance = (collection[0] instanceof Model);

  var is_valid_collection = collection.every(function(v) {
    return (v instanceof Model) === first_is_instance;
  });

  if (!is_valid_collection) {
    return collection;
  }

  if (first_is_instance) {
    Collection.methods.instance.each(Collection.proxy(collection));
  } else {
    Collection.methods.klass.each(Collection.proxy(collection));
  }

  return collection;
}

Collection.proxy = function(collection) {
  return function(method) {
    if (method.getter || method.setter) {

      if (method.getter) {
        collection.__defineGetter__(method.name, Collection.map(method));
      }

      if (method.setter) {
        collection.__defineSetter__(method.name, Collection.map(method));
      }

    } else {
      collection[method.name] = Collection.map(method);
    }
  };
};

Collection.map = function(method) {
  return function() {
    var self = this, args = fun(arguments), callback;

    var returned = [];
    var returned_async = [];
    var returned_async_count = 0;

    var done = function() {
      var _args = fun(arguments);

      returned_async_count += 1;

      _args.each(function(v, i) {
        returned_async[i] = returned_async[i] || [];
        returned_async[i].push(v);
      });

      if ((returned_async_count === self.length) && callback) {
        callback.apply(self, returned_async);
      }
    };

    if (!method.each && args.functions().length) {

      if (method.async) {
        callback = args.functions()[0];
        if (!callback) {
          args.push(done);
        } else {
          args[args.indexOf(callback)] = done;
        }
      }

      // else skip: Function is a constructor
    }

    self.each(function(model) {
      var _return;


      if (method.getter || method.setter) {
        if (method.setter && args.length > 0) {
          _return = (model[method.name] = args[0]);
        } else if (method.getter) {
          _return = model[method.name];
        }
      } else {
        if (model[method.name]) {
          _return = model[method.name].apply(model, args);
        }
      }

      returned.push(_return);
    });

    return returned;
  }
};

Collection.methods = {
  klass: [
    {name: 'name', getter: true},
    {name: 'schema', getter: true, setter: true},
    {name: 'storage', getter: true, setter: true},
    {name: 'validator', getter: true, setter: true},
    {name: 'differ', getter: true, setter: true},
    {name: 'use'},
    {name: 'new'},
    {name: 'create', async: true},
    {name: 'set', async: true},
    {name: 'get', async: true},
    {name: 'del', async: true},
    {name: 'exists', async: true},
    {name: 'end', async: true},
    {name: 'validate', async: true},
    {name: 'diff', async: true},
    {name: 'on', async: true, each: true},
    {name: 'off', async: true, each: true},
    {name: 'emit', async: true}
  ],
  instance: [
    {name: 'name', getter: true},
    {name: 'id', getter: true, setter: true},
    {name: 'type', getter: true},
    {name: 'schema', getter: true}, // MAYBE: setter: true
    {name: 'storage', getter: true}, // MAYBE: setter: true
    {name: 'validator', getter: true}, // MAYBE: setter: true
    {name: 'differ', getter: true}, // MAYBE: setter: true
    // {name: 'use'}, // MAYBE
    {name: 'persisted', getter: true},
    {name: 'unpersisted', getter: true},
    {name: 'changes', getter: true},
    {name: 'changed', getter: true},
    {name: 'valid', getter: true},
    {name: 'invalid', getter: true},
    {name: 'storage', getter: true, setter: true},
    {name: 'validator', getter: true, setter: true},
    {name: 'differ', getter: true, setter: true},
    {name: 'attributes', getter: true, setter: true}, // NOTE: Document([post, article]).attributes.title = 'A' won't work naturally - fix with nested-getters-hack on attributes?
    {name: 'persisted_attributes', getter: true, setter: true},
    {name: 'unchanged', getter: true},
    {name: 'errors', getter: true},
    {name: 'validate', async: true},
    {name: 'diff', async: true},
    {name: 'get'},
    {name: 'set'},
    {name: 'attr'},
    {name: 'reset'},
    {name: 'clear'},
    {name: 'toJSON'},
    {name: 'toString'},
    {name: 'valueOf'},
    // {name: 'inspect'}, // RESERVED: Cannot override this in Node.js; specs pass but when running in REPL this breaks `[].inspect`.
    {name: 'clone'},
    {name: 'destroy', async: true},
    {name: 'save', async: true},
    {name: 'fetch', async: true},
    {name: 'on', async: true, each: true},
    {name: 'off', async: true, each: true},
    {name: 'emit', async: true}
  ]
};

// -----------------------
//  Export
// --------------------

module.exports = Collection;

{'fraction_non_alphanumeric': 0.19518098800748157, 'fraction_numerical': 0.0018703927824843217, 'mean_word_length': 2.6594202898550723, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7011959', 'n_tokens_mistral': 3000, 'n_tokens_neox': 2954, 'n_words': 732}