Breeze Mongo Angular autoGeneratedKey error - angularjs

I'm using Breeze with Angular and MongoDB.
I included all the necessary services and scripts to make sure breeze works with angular and MongoDB.
However, when I try to save my changes I get the following error on the server:
ObjectIds and Guids are the only autoGenerated key types that Breeze currently supports, not: undefined
This error occurs in the mongoSaveHandler.js file of the mongobreeze module:
var keyDataType = entityType.keyDataType;
if (keyDataType === "Guid") {
e._id = createGuid();
} else if (keyDataType == "MongoObjectId") {
// instead of omitting the _id and having mongo update it, we want to set it ourselves so that we can do
// fk fixup before going async
e._id = new ObjectID();
} else {
that._raiseError(new Error("ObjectIds and Guids are the only autoGenerated key types that Breeze currently supports, not: " + keyDataType));
return;
}
I made sure that the id of my object is a mongo id:
function addVisit() {
addType({
name: 'Visit',
dataProperties: {
id: { type: DT.MongoObjectId },
pain: { type: ID },
paper: {type: ID},
consistency: {type: ID}
}
});
}
But indeed when I log the entityType object it has no property keyDataType?
I can get everything to work if I just remove the error. Then my inserted objects look like this in MongoDB:
{ id: 5350d4e704a02e1f04000000,
pain: 50,
consistency: 50,
date: Fri Apr 18 2014 08:31:51 GMT+0100 (WEST),
_id: 5350d4e7101b04a9560e660a },
Meaning they have 2 ids?
When I try to query the database I get a nice response:
[
{
"id": "535052f504a02e79c6000000",
"pain": 50,
"consistency": 50,
"_id": "535052f6f672174a4dffffd4"
},
{
"id": "5350d1bb04a02e4e56000000",
"pain": 50,
"consistency": 50,
"date": "2014-04-18T07:18:19.616Z",
"_id": "5350d1bb101b04a9560e6606"
},
{
"id": "5350d2c104a02e595c000000",
"pain": 50,
"consistency": 50,
"date": "2014-04-18T07:22:41.696Z",
"_id": "5350d2c1101b04a9560e6607"
},
]
But somehow Breeze is unable to import this properly and I get a circular dependency.
Could this have something to do with the double ID's?

Where did you get DT.MongoObjectId from? That isn't listed in the breeze docs as a supported data type so it is returning undefined as a type. If you are properly generating the Id why not just use a string if it is immutable?
id : { type: DT.String }

Try to set a naming convention that would convert breeze's "id" field into mongo's "_id" and vice versa. It will eliminate double ID's
Here's the code for client side:
var convention = new breeze.NamingConvention({
serverPropertyNameToClient: function (serverPropertyName) {
switch (serverPropertyName) {
case '_id':
return 'id';
default :
return serverPropertyName;
}
},
clientPropertyNameToServer: function (clientPropertyName) {
switch (clientPropertyName) {
case 'id':
return '_id';
default:
return clientPropertyName;
}
}
});
convention.setAsDefault();

Related

Mongoose FindOne - only return fields which match condition

I am trying to query my collection of matches (games) and find if a certain user has already sent data to the 'reportMessages' array of Objects.
const results = await Match.findOne({ 'users': req.params.userIdOfReportSender, '_id': req.params.matchId, 'reportMessages.sentBy': req.params.userIdOfReportSender }, 'reportMessages' )
However, the above query returns the following:
{
_id: 5fd382c65d5395e0778f2f8a,
reportMessages: [
{
_id: 5fd610f27ae587189c45b6ca,
content: 'jajatest',
timeStamp: 2020-12-13T13:02:42.102Z,
sentBy: 'XbVvm6g3nsRmPg3P1pBvVl84h6C2'
},
{ sentBy: "'anotheruser123" }
]
}
How can I get it to only return the first reportMessage, i.e. the one sent by XbVvm6g3nsRmPg3P1pBvVl84h6C2?
Mongoose findOne docs (https://mongoosejs.com/docs/api.html#model_Model.findOne) show that you can provide arguments to say which fields to select (in their case 'name length' but don't show a way to only select the fields in case they match a certain condition.
Is this even possible? Tried googling this seemingly easy question for quite some time without success
Kind regards
You can get only the subdocument you want with this aggregation query:
Match.aggregate([
{
$match: { _id: req.params.matchId }
},
{
$project: {
reportMessages: {
$filter: {
input: '$reportMessages',
as: 'msg',
cond: { $eq: ['$$msg.sentBy', req.params.userIdOfReportSender] }
}
}
}
},
{
$project: {
reportMessage: { $arrayElemAt: [ '$reportMessages', 0 ] },
}
},
{ $replaceWith: '$reportMessage' }
]);
Note that you only need to specify the document _id to get a single result, since _ids are unique.

How to publish a collection based on a value of an array value

I have the following data structure in a Meteor app, I would like to create a publication based on the "modelo" value.
{
"_id": "BAnLur25298ytvMdT",
"numero": "97",
"lienzos": 100,
"fechaCorte": "2016-03-29T00:00:00.000Z",
"modelos": [
{
"modelo": "95",
"distribucion": 100
},
{
"modelo": "96",
"distribucion": 100
}
],
"tela": "Jackard"
}
For example :
Meteor.publish('ModelosCorte', function(id) {
return CortesGeneral.find({
modelos: id
});
});
But I want to publish for example all CortesGeneral that have a "modelo": "96" as a value
You need to use dot notation to specify that you want to search for 'modelos.modelo'. Give this a try:
Meteor.publish('ModelosCorte', function (id) {
check(id, String);
return CortesGeneral.find({ 'modelos.modelo': id });
});

How to get data from array in mongoose?

I am new to mongoose node.js and mongoDB, I have a db Schema like
Project:{
projectName:"String",
projectManager:"String",
task:[{
taskName:"String",
timetakeninhrs:"String"
}]
};
So what I want is to get only the details of task with particular task name.
I am writing sql script so that you can know what I want :
Select taskname,timetakeninhrs from project where taskName ='DB create';
The $elemMatch projection operator would come in handy for this:
Project
.where('task.taskName', 'DB create') // or where('task.taskName').equals('DB create').
.select({_id: 0, task: {$elemMatch: {'taskName': 'DB create'}})
.exec(function(err, docs){
var tasks = docs.map(function(doc){ return doc.task[0]; });
console.log(tasks[0].taskName); // 'DB create'
console.log(tasks[0].timetakeninhrs); // '3'
});
In the above, the where() method acts as a static helper method of the Mongoose model that builds up a query using chaining syntax, rather than specifying a JSON object. So
// instead of writing:
Project.find({ 'task.taskName': 'DB create' }, callback);
// you can instead write:
Project.where('task.taskName', 'DB create');
// or
Project.where('task.taskName').equals('DB create');
and then chain the select() method to project the 'task' array field using $elemMatch. In the exec() method (which executes the query asynchronously), you need to pass in a callback which follows the pattern callback(error, results). What results is depends on the operation: For findOne() it is a potentially-null single document, find() a list of documents, count() the number of documents, update() the number of documents affected, etc. In this case this returns an array of documents in the format:
[
/* 0 */
{
"task" : [
{
"taskName" : "DB create",
"timetakeninhrs" : "3"
}
]
},
/* 1 */
{
"task" : [
{
"taskName" : "DB create",
"timetakeninhrs" : "9"
}
]
}
/* etc */
]
In your callback you can do a bit of data manipulation to get an object that only has those properties you specified, hence the use of the native JavaScript map() function to create a new array of objects with those fields
i create this example that can help you:
var async=require('async');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var uri = 'mongodb://localhost/myDb';
mongoose.connect(uri);
// define a schema
var ProjectSchema = new Schema({
projectName: "String",
projectManager: "String",
task: [{
taskName: "String",
timetakeninhrs: "String"
}]
});
// compile our model
var Project = mongoose.model('Project', ProjectSchema);
// create a documents
var Project01 = new Project({
projectName: "Project01",
projectManager: "Manager01",
task: [{
taskName: "tsk01_Project01",
timetakeninhrs: "1111-1111"
}, {
taskName: "tsk02_Project01",
timetakeninhrs: "1111-2222"
}, {
taskName: "tsk03_Project01",
timetakeninhrs: "1111-3333"
}, {
taskName: "tsk04_Project01",
timetakeninhrs: "1111-4444"
}]
});
var Project02 = new Project({
projectName: "Project02",
projectManager: "Manager02",
task: [{
taskName: "tsk01_Project02",
timetakeninhrs: "2222-1111"
}, {
taskName: "tsk02_Project02",
timetakeninhrs: "2222-2222"
}, {
taskName: "tsk03_Project02",
timetakeninhrs: "2222-3333"
}, {
taskName: "tsk04_Project02",
timetakeninhrs: "2222-4444"
}]
});
//delete existing documents and create them again
Project.remove({}, function() {
Project01.save(function() {
Project02.save(function() {
//for example we find taskName: "tsk03_Project02"
Project.find({'task': {$elemMatch: {taskName: "tsk03_Project02"}}},'task.taskname task.timetakeninhrs',function(err, docs) {
if (!err) {
console.log(docs);
}
});
});
});
});

Angularjs: Many-to-Many with Through Relationship

In my app I have Students who can Enroll in Classes.
An Enrollment records the start and end dates along with a reference to which Class.
The Class has details like a class name and description.
Student (1) - (N) Enrollment (1) - (1) Class
Therefore
Student (1) - (N) Class
This is what I would like the object to look like.
{
"studentId": 1,
"name": "Henrietta",
"enrolledClasses": [
{
"enrollmentId": 12,
"startDate": "2015-08-06T17:43:14.000Z",
"endDate": null,
"class":
{
"classId": 15,
"name": "Algebra",
"description": "..."
}
},
"enrollmentId": 13,
"startDate": "2015-08-06T17:44:14.000Z",
"endDate": null,
"class":
{
"classId": 29,
"name": "Physics",
"description": "..."
}
}
}
But my RESTful API cannot (easily) output the full nested structure of the relationship because it's ORM can't do Many to Many with Through relationships. So I get the student and their multiple enrollments, but only a reference to the class for each, not the details. And I need to show the details, not just an Id.
I could wait for the student object to be available and then use the references to make additional calls to the API for each classId to get details, but am not sure how, or even if, to then integrate it with the student object.
This is what I have so far.
function findOne() {
vm.student = StudentsResource.get({
studentId: $stateParams.studentId
});
};
This is what I get back from my API
{
"studentId": 1,
"name": "Henrietta",
"enrolledClasses": [
{
"enrollmentId": 12,
"startDate": "2015-08-06T17:43:14.000Z",
"endDate": null,
"class": 15
},
"enrollmentId": 13,
"startDate": "2015-08-06T17:44:14.000Z",
"endDate": null,
"class": 29
}
}
And for the class details I can them one at a time but haven't figured out how to wait until the student object is available to push them into it. Nor how to properly push them...
{
"classId": 15,
"name": "Algebra",
"description": "..."
}
{
"classId": 29,
"name": "Physics",
"description": "..."
}
Question
Is it good practice to combine the student and class(es) details through the enrollments, as one object?
If so, whats the most clear way to go about it?
If not, what is another effective approach?
Keep in mind that the app will allow changes to the student details and enrollments, but should not allow changes to the details of the class name or description. So if Angular were to send a PUT for the object, it should not try to send the details of any class, only the reference. This would be enforced server side to protect the data, but to avoid errors, the client shouldn't try.
For background, I'm using SailsJS and PostgreSQL for the backend API.
So basically on the Sailsjs side you should override your findOne function in your StudentController.js. This is assuming you are using blueprints.
// StudentController.js
module.exports = {
findOne: function(req, res) {
Student.findOne(req.param('id')).populate('enrollments').then(function(student){
var studentClasses = Class.find({
id: _.pluck(student.enrollments, 'class')
}).then(function (classes) {
return classes
})
return [student, classes]
}).spread(function(student, classes){
var classes = _.indexBy(classes, 'id')
student.enrollments = _.map(student.enrollments, function(enrollment){
enrollment.class = classes[enrollment.class]
return enrollment
})
res.json(200, student)
}).catch(function(err){
if (err) return res.serverError(err)
})
}
}
// Student.js
module.exports = {
attributes: {
enrollments: {
collection: 'enrollment',
via: 'student'
}
// Rest of the attributes..
}
}
// Enrollment.js
module.exports = {
attributes: {
student: {
model: 'student'
}
class: {
model: 'class'
}
// Rest of the attributes...
}
}
// Class.js
module.exports: {
attributes: {
enrollments: {
collection: 'enrollment',
via: 'class'
}
// Rest of the attrubutes
}
}
Explanation:
1. the _.pluck function using Lo-dash retuns an array of classIds and we find all of the classes that we wanted populated. Then we use the promise chain .spread(), create an array indexed by classid and map the classIds to the actual class instances we wanted populated into the enrollments on the student returned from Student.findOne().
2. Return the student with enrollments deep populated with the proper class.
source: Sails.js populate nested associations

Breeze Entity property name mapping

I have a server JSON object that looks like this:
{
"Id": 58,
"ContentTypeId": "0x010800CC86F2447E566746907284C1DE2DE7F5",
"Title": "Rework and Drop Test",
"Priority": "(2) Normal",
"Status": "Waiting on someone else",
"PercentComplete": 0,
"AssignedToId": 93,
"Body": "Some text",
"StartDate": "2015-03-06T05:00:00Z",
"DueDate": "2015-03-13T05:00:00Z"
}
And I am using Angular-Gantt which expects its objects to have the property from where the object above has StartDate and then to where the object above has DueDate. Is there an easy way to map the name of a property on the Breeze entity to the property on JSON object. For example, Kendo UI's model definition has a fromField property that I could use to perform this mapping.
to: {
fromField: 'DueDate'
}
I tried the following in breeze.js
StartDate: {
type: breeze.DataType.DateTime,
name: 'from'
}
and
from: {
type: breeze.DataType.DateTime,
name: 'StartDate'
}
But neither of those worked. I also tried nameOnServer with the same lack of results. I know that I can generate a naming convention object but the idea of hand writing the mapping of these fields on both the client and the server side seems silly to me to have two giant switch statements that do essentially the same thing in reverse and it does not strike me as maintainable. Any suggestions? I hope I am just missing something in the documentation.
I'm using v.0.2.4 of breeze.labs.dataservice.abstractrest.js and v.0.2.3 of breeze.labs.dataservice.sharepoint.js.
Edit:
I did just update to the latest builds and I am still seeing the exact same thing.
Using a naming convention object with the nameOnServer property on my entity works. But I'd rather not do it this way.
function AngularGanttNamingConvention() {
return new breeze.NamingConvention({
name: 'angularGanttNamingConvention',
clientPropertyNameToServer: clientPropertyNameToServer,
serverPropertyNameToClient: serverPropertyNameToClient
});
function clientPropertyNameToServer(propertyName) {
return fixUpClientProperty(propertyName);
}
function fixUpClientProperty(propertyName) {
var newName;
switch (propertyName) {
case "DueDate":
newName = "to";
break;
case "to":
newName = "DueDate";
break;
case "StartDate":
newName = "from";
break;
case "from":
newName = "StartDate";
break;
case "Title":
newName = "name";
break;
case "name":
newName = "Title";
break;
default:
newName = propertyName;
}
return newName;
}
function serverPropertyNameToClient(propertyName) {
return fixUpClientProperty(propertyName);
}
}
AngularGanttNamingConvention().setAsDefault();

Resources