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
Related
I made an http request and got back e.g the below json. I want to read the cars and the shoes objects into separate arrays at the time of the http request.
json
{
"id": 9,
"name": "Zlatan",
"cars": [
{
"type": "ford",
"year": "2019"
},
{
"type": "audi",
"year": "2017"
}
]
"shoes": [
{
"brand": "adidas",
"status": "old"
},
{
"brand": "timberland",
"status": "new"
}
]
}
comp.ts
cars = [];
shoes = [];
//......
getZlatan() {
return this.httpService.getInfo()
.subscribe( data => {
this.objZlatan = data; //this part holds the json obj above
this.cars ....//what to add here
this.shoes ....// here too.
} );
}
Or is there a cleaner way to load the arrays at http request?
Access the cars and shoes properties of the data with simple dot-notation. It might be ideal to check if the data returned is not null with if(condition here) and then perform your logic. If you have more objects and want to bring all cars and shoes under one array then you have to loop through.
getZlatan() {
return this.httpService.getInfo()
.subscribe(data => {
this.objZlatan = data;
this.cars = this.objZlatan.cars;
this.shoes = this.objZlatan.shoes;
});
}
Just use . and type names to access cars and shoes. Let me show an example:
return this.httpService.getInfo()
.subscribe( data => {
this.objZlatan = data; //this part holds the json obj above
this.cars = data.cars;
this.shoes =data.shoes;
} );
I am currently working on a Angular project that makes it possible to create lobbies for different web games. The idea is that the application already gathers players so a web game can be started immediately.
But right now I am running into a problem that is caused by getting data from my Springboot Java API.
The Angular application gets the data correctly but when i try to convert the observable into a normal Game[] after subscribing ofcourse. It makes all the properties of the elements in the Game[] undefined. What is causing this to happen?
The Game Service:
//Returns a list of all games known to the API
getAllGames() :Observable<Game[]>
{
return this.httpClient.get<Game[]>(this.connectionService.getConnectionString()+"/games",this.httpOptions)
}
The Game class:
export class Game {
public Id : String;
public Name: String;
public RedirectURI : String;
public MinUserCount : Number;
public MaxUserCount : Number;
constructor(Id : String,Name : String,RedirectURI : String,MinUserCount : Number,MaxUserCount : Number)
{
this.Id = Id;
this.Name = Name;
this.RedirectURI = RedirectURI;
this.MinUserCount = MinUserCount;
this.MaxUserCount = MaxUserCount;
}
}
The Component:
games: Game[];
//Get all games known to the API
this.gameService.getAllGames().subscribe( elements => this.games = elements )
//This doesn't work all the elements of this.games are undefined.
I have also tried to work with a foreach of the array that gets returned.
With no effect either
games: Game[];
//Get all games known to the API
this.gameService.getAllGames().subscribe(elements => {
elements.forEach(item => {
var game = new Game(item.Id, item.Name, item.RedirectURI, item.MinUserCount, item.MaxUserCount)
this.games.push(game)
})
})
The JSON Result for the GetRequest
[
{
"name": "QuizGame",
"id": "dg217d65126b5e31v6d5v5d15v564d",
"maxUserCount": 4,
"redirectURI": "http://localhost:8082",
"minUserCount": 1
},
{
"name": "RPG",
"id": "dt76TQWR127367125SDYATFHa32615",
"maxUserCount": 10,
"redirectURI": "http://localhost:8083",
"minUserCount": 0
},
{
"name": "Scribble Clone",
"id": "378167e86dsahdgahkj312DJKHDg2d",
"maxUserCount": 9,
"redirectURI": "http://localhost:8084",
"minUserCount": 1
},
{
"name": "WebPonker",
"id": "0o00dhsnvkjzbcxhgjyg23v2gh3dvg",
"maxUserCount": 4,
"redirectURI": "http://localhost:8085",
"minUserCount": 4
}
]
The properties in your JSON response start with a lowercase.
In your Game class, you use properties that start with an uppercase.
I believe the parsing from JSON to a typescript object is case sensitive. Could you try to change the first letter of your properties to lowercase?
I have 2 models in my project, 1 is stores and the other is products, the stores has a reference to products like this:
produtos: [
{ type: mongoose.Schema.ObjectId, ref: 'Produto' }
]
and basicly at the moment i have a object like this:
{
"_id": "589715a3a2030f7bc143ed89",
"nome": "levis2",
"email": "filipecostaa10#gmail.com1",
"password": "1234",
"__v": 8,
"produtos": [
"58971695a2030f7bc143ed8a",
"589716d7a2030f7bc143ed8b",
"58971742a2030f7bc143ed8c",
"58971770a2030f7bc143ed8d",
"589717a1a2030f7bc143ed8e",
"58971848a2030f7bc143ed8f",
"589718f5a2030f7bc143ed90",
"58971937a2030f7bc143ed91"
],
"descricao": "No description for this store"
}
the array produtos has a lot of ids. Those ids are reference to the products; What i need to know now is how i can do a get request to get all the products with those ids, how do i send the request?
here is my router
router.get('/',function(req,res){
Loja.find(function(err,lojas){
if(!lojas){
return res.status(404).json({Error:"Loja nao encontrada"});
}
res.send(lojas);
}).populate("Produto");
})
Seems you are trying to get all stores with populated products field so:
router
.route('/')
.get(function(req, res) {
Loja
.find()
.populate('produtos')
.exec(function(err, lojas) {
if ( err ) {
return handleError(err);
}
res.json(lojas);
});
});
We have created an array of complextype(Carrier field) objects. See below metadata
{ shortName : 'Person',
namespace : 'Demo',
autoGeneratedKeyType : breeze.AutoGeneratedKeyType.Identity,
"dataProperties": [
{
"name": "carriers",
"complexTypeName":"Carrier:#Test",
"isScalar":false
}]
}
The Carrier entity is defined as below:
{
"shortName": "Carrier",
"namespace": "Test",
"isComplexType": true,
"dataProperties": [
{
"name": "Testing",
"isScalar":true,
"dataType": "String"
}
]
}
We have the following matching data for the above entities:
{
carriers: [
{
Testing : 'InputBox1'
},
{
Testing : 'InputBox2'
}
]
}
We are trying to dynamically add the complextype object(Carrier) to the above carriers array by using the following approach:
var test = {
"Testing" : "Test"
};
var result = manager.createEntity('Carrier', test);
The above code throws an exception(undefined is not a function) inside breeze.debug.js at line number 12457(see below code)
entity = entityType.createEntity(initialValues);
The exception is thrown since the complextype entity does not have 'createEntity' function in it.
What are we missing here?
Excellent question - Sorry I didn't have a chance to address this earlier.
When adding a complexType object you need to use the createInstance() method instead of the createEntity.
var thisEntityType = manager.metadataStore.getEntityType('Carrier');
var thisEntity = thisEntityType.createInstance(initialValues);
Basically you get the complexType and then create an instance of it using the values you want assigned. Keep in mind the initial values should be a hash object of course. Often I will include a helper function to do this for me like this -
function createComplexType(entityType, constructorProperties) {
var thisEntityType = manager.metadataStore.getEntityType(entityType);
var thisEntity = thisEntityType.createInstance(constructorProperties);
return thisEntity;
}
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();