<commit_before>/**
 * Module exports class Model
 */
module.exports = DataAccessObject;

/**
 * Module dependencies
 */
var util = require('util');
var jutil = require('./jutil');
var validations = require('./validations.js');
var ValidationError = validations.ValidationError;
var List = require('./list.js');
require('./relations.js');
var Inclusion = require('./include.js');
var Relation = require('./relations.js');
var geo = require('./geo');
var Memory = require('./connectors/memory').Memory;
var utils = require('./utils');
var fieldsToArray = utils.fieldsToArray;
var removeUndefined = utils.removeUndefined;


/**
 * DAO class - base class for all persist objects
 * provides **common API** to access any database connector.
 * This class describes only abstract behavior layer, refer to `lib/connectors/*.js`
 * to learn more about specific connector implementations
 *
 * `DataAccessObject` mixes `Inclusion` classes methods
 *
 * @constructor
 * @param {Object} data - initial object data
 */
function DataAccessObject() {
    if(DataAccessObject._mixins) {
        var self = this;
        var args = arguments;
        DataAccessObject._mixins.forEach(function(m) {
            m.call(self, args);
        });
    }
}

function idName(m) {
    return m.getDataSource().idName 
    ? m.getDataSource().idName(m.modelName) : 'id';
}

function getIdValue(m, data) {
    return data && data[m.getDataSource().idName(m.modelName)];
}

function setIdValue(m, data, value) {
    if(data) {
        data[idName(m)] = value;
    }
}

DataAccessObject._forDB = function (data) {
    if(!(this.getDataSource().isRelational && this.getDataSource().isRelational())) {
        return data;
    }
    var res = {};
    for(var propName in data) {
        var type = this.getPropertyType(propName);
        if (type === 'JSON' || type === 'Any' || type === 'Object' || data[propName] instanceof Array) {
            res[propName] = JSON.stringify(data[propName]);
        } else {
            res[propName] = data[propName];
        }
    }
    return res;
};

/**
 * Create new instance of Model class, saved in database
 *
 * @param data [optional]
 * @param callback(err, obj)
 * callback called with arguments:
 *
 *   - err (null or Error)
 *   - instance (null or Model)
 */
DataAccessObject.create = function (data, callback) {
    if (stillConnecting(this.getDataSource(), this, arguments)) return;

    var Model = this;
    var modelName = Model.modelName;

    if (typeof data === 'function') {
        callback = data;
        data = {};
    }

    if (typeof callback !== 'function') {
        callback = function () {};
    }

    if (!data) {
        data = {};
    }

    if (Array.isArray(data)) {
        var instances = [];
        var errors = Array(data.length);
        var gotError = false;
        var wait = data.length;
        if (wait === 0) callback(null, []);

        var instances = [];
        for (var i = 0; i < data.length; i += 1) {
            (function(d, i) {
                instances.push(Model.create(d, function(err, inst) {
                    if (err) {
                        errors[i] = err;
                        gotError = true;
                    }
                    modelCreated();
                }));
            })(data[i], i);
        }

        return instances;

        function modelCreated() {
            if (--wait === 0) {
                callback(gotError ? errors : null, instances);
            }
        }
    }


    var obj;
    // if we come from save
    if (data instanceof Model && !getIdValue(this, data)) {
        obj = data;
    } else {
        obj = new Model(data);
    }
    data = obj.toObject(true);

    // validation required
    obj.isValid(function(valid) {
        if (valid) {
            create();
        } else {
            callback(new ValidationError(obj), obj);
        }
    }, data);

    function create() {
        obj.trigger('create', function(createDone) {
            obj.trigger('save', function(saveDone) {

                var _idName = idName(Model);
                this._adapter().create(modelName, this.constructor._forDB(obj.toObject(true)), function (err, id, rev) {
                    if (id) {
                        obj.__data[_idName] = id;
                        obj.__dataWas[_idName] = id;
                        defineReadonlyProp(obj, _idName, id);
                    }
                    if (rev) {
                        obj._rev = rev;
                    }
                    if (err) {
                        return callback(err, obj);
                    }
                    saveDone.call(obj, function () {
                        createDone.call(obj, function () {
                            callback(err, obj);
                        });
                    });
                }, obj);
            }, obj);
        }, obj);
    }

    // for chaining
    return obj;
};

/*!
 * Configure the remoting attributes for a given function
 * @param {Function} fn The function
 * @param {Object} options The options
 * @private
 */
function setRemoting(fn, options) {
    options = options || {};
    for(var opt in options) {
        if(options.hasOwnProperty(opt)) {
            fn[opt] = options[opt];
        }
    }
    fn.shared = true;
}

setRemoting(DataAccessObject.create, {
    description: 'Create a new instance of the model and persist it into the data source',
    accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
    returns: {arg: 'data', type: 'object', root: true},
    http: {verb: 'post', path: '/'}
});

function stillConnecting(dataSource, obj, args) {
    return dataSource.ready(obj, args);
}

/**
 * Update or insert a model instance
 * @param {Object} data The model instance data
 * @param {Function} [callback] The callback function
 */
DataAccessObject.upsert = DataAccessObject.updateOrCreate = function upsert(data, callback) {
    if (stillConnecting(this.getDataSource(), this, arguments)) return;

    var Model = this;
    if (!getIdValue(this, data)) return this.create(data, callback);
    if (this.getDataSource().connector.updateOrCreate) {
        var inst = new Model(data);
        this.getDataSource().connector.updateOrCreate(Model.modelName, inst.toObject(true), function (err, data) {
            var obj;
            if (data) {
                inst._initProperties(data, false);
                obj = inst;
            } else {
                obj = null;
            }
            callback(err, obj);
        });
    } else {
        this.findById(getIdValue(this, data), function (err, inst) {
            if (err) return callback(err);
            if (inst) {
                inst.updateAttributes(data, callback);
            } else {
                var obj = new Model(data);
                obj.save(data, callback);
            }
        });
    }
};

// upsert ~ remoting attributes
setRemoting(DataAccessObject.upsert, {
    description: 'Update an existing model instance or insert a new one into the data source',
    accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
    returns: {arg: 'data', type: 'object', root: true},
    http: {verb: 'put', path: '/'}
});


/**
 * Find one record, same as `all`, limited by 1 and return object, not collection,
 * if not found, create using data provided as second argument
 * 
 * @param {Object} query - search conditions: {where: {test: 'me'}}.
 * @param {Object} data - object to create.
 * @param {Function} cb - callback called with (err, instance)
 */
DataAccessObject.findOrCreate = function findOrCreate(query, data, callback) {
    if (query === undefined) {
        query = {where: {}};
    }
    if (typeof data === 'function' || typeof data === 'undefined') {
        callback = data;
        data = query && query.where;
    }
    if (typeof callback === 'undefined') {
        callback = function () {};
    }

    var t = this;
    this.findOne(query, function (err, record) {
        if (err) return callback(err);
        if (record) return callback(null, record);
        t.create(data, callback);
    });
};

/**
 * Check whether a model instance exists in database
 *
 * @param {id} id - identifier of object (primary key value)
 * @param {Function} cb - callbacl called with (err, exists: Bool)
 */
DataAccessObject.exists = function exists(id, cb) {
    if (stillConnecting(this.getDataSource(), this, arguments)) return;

    if (id !== undefined && id !== null && id !== '') {
        this.dataSource.connector.exists(this.modelName, id, cb);
    } else {
        cb(new Error('Model::exists requires the id argument'));
    }
};

// exists ~ remoting attributes
setRemoting(DataAccessObject.exists, {
    description: 'Check whether a model instance exists in the data source',
    accepts: {arg: 'id', type: 'any', description: 'Model id', required: true},
    returns: {arg: 'exists', type: 'any'},
    http: {verb: 'get', path: '/:id/exists'}
});

/**
 * Find object by id
 *
 * @param {*} id - primary key value
 * @param {Function} cb - callback called with (err, instance)
 */
DataAccessObject.findById = function find(id, cb) {
    if (stillConnecting(this.getDataSource(), this, arguments)) return;

    this.getDataSource().connector.find(this.modelName, id, function (err, data) {
        var obj = null;
        if (data) {
            if (!getIdValue(this, data)) {
                setIdValue(this, data, id);
            }
            obj = new this();
            obj._initProperties(data, false);
        }
        cb(err, obj);
    }.bind(this));
};

// find ~ remoting attributes
setRemoting(DataAccessObject.findById, {
    description: 'Find a model instance by id from the data source',
    accepts: {arg: 'id', type: 'any', description: 'Model id', required: true},
    returns: {arg: 'data', type: 'any', root: true},
    http: {verb: 'get', path: '/:id'}
});


// alias function for backwards compat.
DataAccessObject.all = function () {
  DataAccessObject.find.apply(this, arguments);
};

/**
 * Find all instances of Model, matched by query
 * make sure you have marked as `index: true` fields for filter or sort
 *
 * @param {Object} params (optional)
 *
 * - where: Object `{ key: val, key2: {gt: 'val2'}}`
 * - include: String, Object or Array. See DataAccessObject.include documentation.<commit_msg>REST call of DataAccessObject.findById returns 404<commit_after>/**
 * Module exports class Model
 */
module.exports = DataAccessObject;

/**
 * Module dependencies
 */
var util = require('util');
var jutil = require('./jutil');
var validations = require('./validations.js');
var ValidationError = validations.ValidationError;
var List = require('./list.js');
require('./relations.js');
var Inclusion = require('./include.js');
var Relation = require('./relations.js');
var geo = require('./geo');
var Memory = require('./connectors/memory').Memory;
var utils = require('./utils');
var fieldsToArray = utils.fieldsToArray;
var removeUndefined = utils.removeUndefined;


/**
 * DAO class - base class for all persist objects
 * provides **common API** to access any database connector.
 * This class describes only abstract behavior layer, refer to `lib/connectors/*.js`
 * to learn more about specific connector implementations
 *
 * `DataAccessObject` mixes `Inclusion` classes methods
 *
 * @constructor
 * @param {Object} data - initial object data
 */
function DataAccessObject() {
    if(DataAccessObject._mixins) {
        var self = this;
        var args = arguments;
        DataAccessObject._mixins.forEach(function(m) {
            m.call(self, args);
        });
    }
}

function idName(m) {
    return m.getDataSource().idName 
    ? m.getDataSource().idName(m.modelName) : 'id';
}

function getIdValue(m, data) {
    return data && data[m.getDataSource().idName(m.modelName)];
}

function setIdValue(m, data, value) {
    if(data) {
        data[idName(m)] = value;
    }
}

DataAccessObject._forDB = function (data) {
    if(!(this.getDataSource().isRelational && this.getDataSource().isRelational())) {
        return data;
    }
    var res = {};
    for(var propName in data) {
        var type = this.getPropertyType(propName);
        if (type === 'JSON' || type === 'Any' || type === 'Object' || data[propName] instanceof Array) {
            res[propName] = JSON.stringify(data[propName]);
        } else {
            res[propName] = data[propName];
        }
    }
    return res;
};

/**
 * Create new instance of Model class, saved in database
 *
 * @param data [optional]
 * @param callback(err, obj)
 * callback called with arguments:
 *
 *   - err (null or Error)
 *   - instance (null or Model)
 */
DataAccessObject.create = function (data, callback) {
    if (stillConnecting(this.getDataSource(), this, arguments)) return;

    var Model = this;
    var modelName = Model.modelName;

    if (typeof data === 'function') {
        callback = data;
        data = {};
    }

    if (typeof callback !== 'function') {
        callback = function () {};
    }

    if (!data) {
        data = {};
    }

    if (Array.isArray(data)) {
        var instances = [];
        var errors = Array(data.length);
        var gotError = false;
        var wait = data.length;
        if (wait === 0) callback(null, []);

        var instances = [];
        for (var i = 0; i < data.length; i += 1) {
            (function(d, i) {
                instances.push(Model.create(d, function(err, inst) {
                    if (err) {
                        errors[i] = err;
                        gotError = true;
                    }
                    modelCreated();
                }));
            })(data[i], i);
        }

        return instances;

        function modelCreated() {
            if (--wait === 0) {
                callback(gotError ? errors : null, instances);
            }
        }
    }


    var obj;
    // if we come from save
    if (data instanceof Model && !getIdValue(this, data)) {
        obj = data;
    } else {
        obj = new Model(data);
    }
    data = obj.toObject(true);

    // validation required
    obj.isValid(function(valid) {
        if (valid) {
            create();
        } else {
            callback(new ValidationError(obj), obj);
        }
    }, data);

    function create() {
        obj.trigger('create', function(createDone) {
            obj.trigger('save', function(saveDone) {

                var _idName = idName(Model);
                this._adapter().create(modelName, this.constructor._forDB(obj.toObject(true)), function (err, id, rev) {
                    if (id) {
                        obj.__data[_idName] = id;
                        obj.__dataWas[_idName] = id;
                        defineReadonlyProp(obj, _idName, id);
                    }
                    if (rev) {
                        obj._rev = rev;
                    }
                    if (err) {
                        return callback(err, obj);
                    }
                    saveDone.call(obj, function () {
                        createDone.call(obj, function () {
                            callback(err, obj);
                        });
                    });
                }, obj);
            }, obj);
        }, obj);
    }

    // for chaining
    return obj;
};

/*!
 * Configure the remoting attributes for a given function
 * @param {Function} fn The function
 * @param {Object} options The options
 * @private
 */
function setRemoting(fn, options) {
    options = options || {};
    for(var opt in options) {
        if(options.hasOwnProperty(opt)) {
            fn[opt] = options[opt];
        }
    }
    fn.shared = true;
}

setRemoting(DataAccessObject.create, {
    description: 'Create a new instance of the model and persist it into the data source',
    accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
    returns: {arg: 'data', type: 'object', root: true},
    http: {verb: 'post', path: '/'}
});

function stillConnecting(dataSource, obj, args) {
    return dataSource.ready(obj, args);
}

/**
 * Update or insert a model instance
 * @param {Object} data The model instance data
 * @param {Function} [callback] The callback function
 */
DataAccessObject.upsert = DataAccessObject.updateOrCreate = function upsert(data, callback) {
    if (stillConnecting(this.getDataSource(), this, arguments)) return;

    var Model = this;
    if (!getIdValue(this, data)) return this.create(data, callback);
    if (this.getDataSource().connector.updateOrCreate) {
        var inst = new Model(data);
        this.getDataSource().connector.updateOrCreate(Model.modelName, inst.toObject(true), function (err, data) {
            var obj;
            if (data) {
                inst._initProperties(data, false);
                obj = inst;
            } else {
                obj = null;
            }
            callback(err, obj);
        });
    } else {
        this.findById(getIdValue(this, data), function (err, inst) {
            if (err) return callback(err);
            if (inst) {
                inst.updateAttributes(data, callback);
            } else {
                var obj = new Model(data);
                obj.save(data, callback);
            }
        });
    }
};

// upsert ~ remoting attributes
setRemoting(DataAccessObject.upsert, {
    description: 'Update an existing model instance or insert a new one into the data source',
    accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
    returns: {arg: 'data', type: 'object', root: true},
    http: {verb: 'put', path: '/'}
});


/**
 * Find one record, same as `all`, limited by 1 and return object, not collection,
 * if not found, create using data provided as second argument
 * 
 * @param {Object} query - search conditions: {where: {test: 'me'}}.
 * @param {Object} data - object to create.
 * @param {Function} cb - callback called with (err, instance)
 */
DataAccessObject.findOrCreate = function findOrCreate(query, data, callback) {
    if (query === undefined) {
        query = {where: {}};
    }
    if (typeof data === 'function' || typeof data === 'undefined') {
        callback = data;
        data = query && query.where;
    }
    if (typeof callback === 'undefined') {
        callback = function () {};
    }

    var t = this;
    this.findOne(query, function (err, record) {
        if (err) return callback(err);
        if (record) return callback(null, record);
        t.create(data, callback);
    });
};

/**
 * Check whether a model instance exists in database
 *
 * @param {id} id - identifier of object (primary key value)
 * @param {Function} cb - callbacl called with (err, exists: Bool)
 */
DataAccessObject.exists = function exists(id, cb) {
    if (stillConnecting(this.getDataSource(), this, arguments)) return;

    if (id !== undefined && id !== null && id !== '') {
        this.dataSource.connector.exists(this.modelName, id, cb);
    } else {
        cb(new Error('Model::exists requires the id argument'));
    }
};

// exists ~ remoting attributes
setRemoting(DataAccessObject.exists, {
    description: 'Check whether a model instance exists in the data source',
    accepts: {arg: 'id', type: 'any', description: 'Model id', required: true},
    returns: {arg: 'exists', type: 'any'},
    http: {verb: 'get', path: '/:id/exists'}
});

/**
 * Find object by id
 *
 * @param {*} id - primary key value
 * @param {Function} cb - callback called with (err, instance)
 */
DataAccessObject.findById = function find(id, cb) {
    if (stillConnecting(this.getDataSource(), this, arguments)) return;

    this.getDataSource().connector.find(this.modelName, id, function (err, data) {
        var obj = null;
        if (data) {
            if (!getIdValue(this, data)) {
                setIdValue(this, data, id);
            }
            obj = new this();
            obj._initProperties(data, false);
        }
        cb(err, obj);
    }.bind(this));
};

// find ~ remoting attributes
setRemoting(DataAccessObject.findById, {
    description: 'Find a model instance by id from the data source',
    accepts: {arg: 'id', type: 'any', description: 'Model id', required: true},
    returns: {arg: 'data', type: 'any', root: true},
    http: {verb: 'get', path: '/:id'},
    rest: {after: convertNullToNotFoundError}
});

function convertNullToNotFoundError(ctx, cb) {
  if (ctx.result !== null) return cb();

  var modelName = ctx.method.sharedClass.name;
  var id = ctx.getArgByName('id');
  var msg = 'Unkown "' + modelName + '" id "' + id + '".';
  var error = new Error(msg);
  error.statusCode = error.status = 404;
  cb(error);
}

// alias function for backwards compat.
DataAccessObject.all = function () {
  DataAccessObject.find.apply(this, arguments);
};

/**
 * Find all instances of Model, matched by query
 * make sure you have marked as `index: true` fields for filter or sort
 *
 * @param {Object} params (optional)
 *
 * - where: Object `{ key: val, key2: {gt: 'val2'}}`
 * - include: String, Object or Array. See DataAccessObject.include documentation.
{'fraction_non_alphanumeric': 0.1405122399886798, 'fraction_numerical': 0.0009433517286920428, 'mean_word_length': 2.286112833230006, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, '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': '7001060', 'n_tokens_mistral': 6089, 'n_tokens_neox': 5954, 'n_words': 1757}