Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 62 additions & 11 deletions examples/relations.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ var DataSource = require('../index').DataSource;
var ds = new DataSource('memory');

var Order = ds.createModel('Order', {
customerId: Number,
items: [String],
orderDate: Date
});

Expand All @@ -12,8 +12,11 @@ var Customer = ds.createModel('Customer', {

Order.belongsTo(Customer);

var order1, order2, order3;

Customer.create({name: 'John'}, function (err, customer) {
Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) {
Order.create({customerId: customer.id, orderDate: new Date(), items: ['Book']}, function (err, order) {
order1 = order;
order.customer(console.log);
order.customer(true, console.log);

Expand All @@ -22,20 +25,34 @@ Customer.create({name: 'John'}, function (err, customer) {
order.customer(console.log);
});
});

Order.create({orderDate: new Date(), items: ['Phone']}, function (err, order) {

order.customer.create({name: 'Smith'}, function(err, customer2) {
console.log(order, customer2);
order.save(function(err, order) {
order2 = order;
});
});

var customer3 = order.customer.build({name: 'Tom'});
console.log('Customer 3', customer3);
});
});

Customer.hasMany(Order, {as: 'orders', foreignKey: 'customerId'});

Customer.create({name: 'Ray'}, function (err, customer) {
Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) {
order3 = order;
customer.orders(console.log);
customer.orders.create({orderDate: new Date()}, function (err, order) {
console.log(order);
Customer.include([customer], 'orders', function (err, results) {
console.log('Results: ', results);
});
customer.orders.findById('2', console.log);
customer.orders.destroy('2', console.log);
customer.orders.findById(order3.id, console.log);
customer.orders.destroy(order3.id, console.log);
});
});
});
Expand All @@ -60,13 +77,32 @@ Appointment.belongsTo(Physician);
Physician.hasMany(Patient, {through: Appointment});
Patient.hasMany(Physician, {through: Appointment});

Physician.create({name: 'Smith'}, function (err, physician) {
Patient.create({name: 'Mary'}, function (err, patient) {
Appointment.create({appointmentDate: new Date(), physicianId: physician.id, patientId: patient.id},
function (err, appt) {
physician.patients(console.log);
patient.physicians(console.log);
Physician.create({name: 'Dr John'}, function (err, physician1) {
Physician.create({name: 'Dr Smith'}, function (err, physician2) {
Patient.create({name: 'Mary'}, function (err, patient1) {
Patient.create({name: 'Ben'}, function (err, patient2) {
Appointment.create({appointmentDate: new Date(), physicianId: physician1.id, patientId: patient1.id},
function (err, appt1) {
Appointment.create({appointmentDate: new Date(), physicianId: physician1.id, patientId: patient2.id},
function (err, appt2) {
physician1.patients(console.log);
physician1.patients({where: {name: 'Mary'}}, console.log);
patient1.physicians(console.log);

// Build an appointment?
var patient3 = patient1.physicians.build({name: 'Dr X'});
console.log('Physician 3: ', patient3, patient3.constructor.modelName);

// Create a physician?
patient1.physicians.create({name: 'Dr X'}, function(err, patient4) {
console.log('Physician 4: ', patient4, patient4.constructor.modelName);
});


});
});
});
});
});
});

Expand All @@ -84,7 +120,22 @@ Part.hasAndBelongsToMany(Assembly);
Assembly.create({name: 'car'}, function (err, assembly) {
Part.create({partNumber: 'engine'}, function (err, part) {
assembly.parts.add(part, function (err) {
assembly.parts(console.log);
assembly.parts(function(err, parts) {
console.log('Parts: ', parts);
});

// Build an part?
var part3 = assembly.parts.build({partNumber: 'door'});
console.log('Part3: ', part3, part3.constructor.modelName);

// Create a part?
assembly.parts.create({partNumber: 'door'}, function(err, part4) {
console.log('Part4: ', part4, part4.constructor.modelName);

Assembly.find({include: 'parts'}, function(err, assemblies) {
console.log('Assemblies: ', assemblies);
});
});
});

});
Expand Down
28 changes: 23 additions & 5 deletions lib/connectors/memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ Memory.prototype.connect = function (callback) {
}
};

function serialize(obj) {
if(obj === null || obj === undefined) {
return obj;
}
return JSON.stringify(obj);
}

function deserialize(dbObj) {
if(dbObj === null || dbObj === undefined) {
return dbObj;
}
if(typeof dbObj === 'string') {
return JSON.parse(dbObj);
} else {
return dbObj;
}
}

Memory.prototype.loadFromFile = function(callback) {
var self = this;
if (self.settings.file) {
Expand Down Expand Up @@ -142,7 +160,7 @@ Memory.prototype.create = function create(model, data, callback) {
if(!this.cache[model]) {
this.cache[model] = {};
}
this.cache[model][id] = JSON.stringify(data);
this.cache[model][id] = serialize(data);
this.saveToFile(id, callback);
};

Expand All @@ -161,7 +179,7 @@ Memory.prototype.updateOrCreate = function (model, data, callback) {
};

Memory.prototype.save = function save(model, data, callback) {
this.cache[model][this.getIdValue(model, data)] = JSON.stringify(data);
this.cache[model][this.getIdValue(model, data)] = serialize(data);
this.saveToFile(data, callback);
};

Expand All @@ -185,7 +203,7 @@ Memory.prototype.destroy = function destroy(model, id, callback) {

Memory.prototype.fromDb = function (model, data) {
if (!data) return null;
data = JSON.parse(data);
data = deserialize(data);
var props = this._models[model].properties;
for (var key in data) {
var val = data[key];
Expand Down Expand Up @@ -374,8 +392,8 @@ Memory.prototype.updateAttributes = function updateAttributes(model, id, data, c
this.setIdValue(model, data, id);

var cachedModels = this.cache[model];
var modelAsString = cachedModels && this.cache[model][id];
var modelData = modelAsString && JSON.parse(modelAsString);
var modelData = cachedModels && this.cache[model][id];
modelData = modelData && deserialize(modelData);

if (modelData) {
this.save(model, merge(modelData, data), cb);
Expand Down
99 changes: 54 additions & 45 deletions lib/dao.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,8 +458,10 @@ DataAccessObject._coerce = function (where) {

/**
* Find all instances of Model, matched by query
* make sure you have marked as `index: true` fields for filter or sort.
* The params object:
* make sure you have marked as `index: true` fields for filter or sort
*
* @param {Object} [query] the query object
*
* - where: Object `{ key: val, key2: {gt: 'val2'}}`
* - include: String, Object or Array. See `DataAccessObject.include()`.
* - order: String
Expand All @@ -470,36 +472,36 @@ DataAccessObject._coerce = function (where) {
* @param {Function} callback (required) called with two arguments: err (null or Error), array of instances
*/

DataAccessObject.find = function find(params, cb) {
DataAccessObject.find = function find(query, cb) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;

if (arguments.length === 1) {
cb = params;
params = null;
cb = query;
query = null;
}
var constr = this;

params = params || {};
query = query || {};

if (params.where) {
params.where = this._coerce(params.where);
if (query.where) {
query.where = this._coerce(query.where);
}

var fields = params.fields;
var near = params && geo.nearFilter(params.where);
var fields = query.fields;
var near = query && geo.nearFilter(query.where);
var supportsGeo = !!this.getDataSource().connector.buildNearFilter;

// normalize fields as array of included property names
if (fields) {
params.fields = fieldsToArray(fields, Object.keys(this.definition.properties));
query.fields = fieldsToArray(fields, Object.keys(this.definition.properties));
}

params = removeUndefined(params);
query = removeUndefined(query);
if (near) {
if (supportsGeo) {
// convert it
this.getDataSource().connector.buildNearFilter(params, near);
} else if (params.where) {
this.getDataSource().connector.buildNearFilter(query, near);
} else if (query.where) {
// do in memory query
// using all documents
this.getDataSource().connector.all(this.modelName, {}, function (err, data) {
Expand All @@ -521,7 +523,7 @@ DataAccessObject.find = function find(params, cb) {
});
});

memory.all(modelName, params, cb);
memory.all(modelName, query, cb);
} else {
cb(null, []);
}
Expand All @@ -532,24 +534,24 @@ DataAccessObject.find = function find(params, cb) {
}
}

this.getDataSource().connector.all(this.modelName, params, function (err, data) {
this.getDataSource().connector.all(this.modelName, query, function (err, data) {
if (data && data.forEach) {
data.forEach(function (d, i) {
var obj = new constr();

obj._initProperties(d, {fields: params.fields});
obj._initProperties(d, {fields: query.fields});

if (params && params.include) {
if (params.collect) {
if (query && query.include) {
if (query.collect) {
// The collect property indicates that the query is to return the
// standlone items for a related model, not as child of the parent object
// For example, article.tags
obj = obj.__cachedRelations[params.collect];
obj = obj.__cachedRelations[query.collect];
} else {
// This handles the case to return parent items including the related
// models. For example, Article.find({include: 'tags'}, ...);
// Try to normalize the include
var includes = params.include || [];
var includes = query.include || [];
if (typeof includes === 'string') {
includes = [includes];
} else if (!Array.isArray(includes) && typeof includes === 'object') {
Expand Down Expand Up @@ -594,19 +596,19 @@ setRemoting(DataAccessObject.find, {
/**
* Find one record, same as `all`, limited by 1 and return object, not collection
*
* @param {Object} params Search conditions: {where: {test: 'me'}}
* @param {Function} cb Callback called with (err, instance)
* @param {Object} query - search conditions: {where: {test: 'me'}}
* @param {Function} cb - callback called with (err, instance)
*/
DataAccessObject.findOne = function findOne(params, cb) {
DataAccessObject.findOne = function findOne(query, cb) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;

if (typeof params === 'function') {
cb = params;
params = {};
if (typeof query === 'function') {
cb = query;
query = {};
}
params = params || {};
params.limit = 1;
this.find(params, function (err, collection) {
query = query || {};
query.limit = 1;
this.find(query, function (err, collection) {
if (err || !collection || !collection.length > 0) return cb(err, null);
cb(err, collection[0]);
});
Expand Down Expand Up @@ -730,7 +732,6 @@ DataAccessObject.prototype.save = function (options, callback) {

var inst = this;
var data = inst.toObject(true);
var Model = this.constructor;
var modelName = Model.modelName;

if (!getIdValue(Model, this)) {
Expand Down Expand Up @@ -844,7 +845,7 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, cb
if (stillConnecting(this.getDataSource(), this, arguments)) return;

var inst = this;
var Model = this.constructor
var Model = this.constructor;
var model = Model.modelName;

if (typeof data === 'function') {
Expand Down Expand Up @@ -911,19 +912,15 @@ setRemoting(DataAccessObject.prototype.updateAttributes, {
* @param {Function} callback Called with (err, instance) arguments
*/
DataAccessObject.prototype.reload = function reload(callback) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;
if (stillConnecting(this.getDataSource(), this, arguments)) {
return;
}

this.constructor.findById(getIdValue(this.constructor, this), callback);
};

/*
setRemoting(DataAccessObject.prototype.reload, {
description: 'Reload a model instance from the data source',
returns: {arg: 'data', type: 'object', root: true}
});
*/

/**
/*!
* Define readonly property on object
*
* @param {Object} obj
Expand All @@ -941,13 +938,25 @@ function defineReadonlyProp(obj, key, value) {

var defineScope = require('./scope.js').defineScope;

/*!
* Define scope. N.B. Not clear if this needs to be exposed in API doc.
/**
* Define a scope for the model class. Scopes enable you to specify commonly-used
* queries that you can reference as method calls on a model.
*
* @param {String} name The scope name
* @param {Object} query The query object for DataAccessObject.find()
* @param {ModelClass} [targetClass] The model class for the query, default to
* the declaring model
*/
DataAccessObject.scope = function (name, filter, targetClass) {
defineScope(this, targetClass || this, name, filter);
DataAccessObject.scope = function (name, query, targetClass) {
defineScope(this, targetClass || this, name, query);
};

// jutil.mixin(DataAccessObject, validations.Validatable);
/*!
* Add 'include'
*/
jutil.mixin(DataAccessObject, Inclusion);

/*!
* Add 'relation'
*/
jutil.mixin(DataAccessObject, Relation);
Loading