I'm creating an Android App (A baseball app) where I'm using MongoDB to store my data. the way I want my JSON data to be stored into the database is like this.
{"email#domain.com":{
"coachName": "Smith"
players:[
player1:{
"throws_":"n\/a",
"position":"position not set",
"number":"1",
"playerNum":"H8E83NxRo6",
"bats":"n\/a",
"team_name":"Team",
"name":"Name"}
player2:{
"throws_":"n\/a",
"position":"position not set",
"number":"1",
"playerNum":"H8E83NxRo6",
"bats":"n\/a",
"team_name":"Team",
"name":"Name"}
]
}
sorry if there is any syntax error, but essentially that is the layout i want for the JSON. Where the mongo page "id" is the persons email. and where "players" is an array of the list of players the coach has.
My question is, how can I
properly setup the Mongoose schema to look like this?
when the app sends the JSON data, how can I parse it to store the data?
and if possible (ill try and figure this part on my own if no one can) if multiple players are being added at once, how can i store them if there's already players in the array?
All of this is backend/server side, I have the android working properly, i just need help with storing it to look like this in JavaScript.
You dont want to use a dynamic variable as a field name. I'm talking about the email you have "email#domain.com". This wouldn't be good because how will you find the object. Its not common to search for object by there fields, you use the field name to describe what it is your looking for. You will need 2 models. One for player and one for coach. Coach refrences a Player in its Players array field.
If you format your JSON correctly you wont have to do any parsing, just make sure the JSON you are sending is correctly formatted.
Look at the addPlayer function.
Controller file (Answer for questions 2 and 3)
create = function(req, res) {
var coach = new Coach(req.body);
coach.user = req.user;
coach.save(function(err) {
if (err) {
return res.status(400).send({
// Put your error message here
});
} else {
res.json(coach);
}
});
};
read = function(req, res) {
res.json(req.coach);
};
addPlayer = function(req, res) {
var coach = req.coach;
console.log('updating coach', req.coach);
var player = new Player(req.newPlayer);
coach.players.push(newPlayer);
coach.save(function(err) {
if (err) {
return res.status(400).send({
// Put your error message here
});
} else {
res.json(coach);
}
});
};
Player
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Player Schema
*/
var PlayerSchema = new Schema({
created: {
type: Date,
default: Date.now
},
throws: {
type: Number,
},
name: {
type: String,
default: '',
trim: true
},
position: {
type: String,
default: '',
trim: true
},
playerNum: {
type: String,
default: '',
trim: true
},
position: {
type: Number,
default: 0,
trim: true
},
teamName: {
type: String,
default: '',
trim: true
}
});
mongoose.model('Player', PlayerSchema);
Coach Schema
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Coach Schema
*/
var CoachSchema = new Schema({
created: {
type: Date,
default: Date.now
},
email: {
type: String,
unique: true,
lowercase: true,
trim: true,
default: ''
},
name: {
type: String,
default: '',
trim: true
},
players: [{
type: Schema.ObjectId,
ref: 'Player'
}
});
mongoose.model('Coach', CoachSchema);
Related
I'm learning to use Node.js + Express to build a REST API. In this API
I have the following method:
apiRouter.route('/training/session/byId/:id_session')
// ===== GET =======
.get(function(req, res) {
//Get the session
Session.findById(req.params.id_session, function(err, session) {
//Get an array of exercise associated with the session
Exercise.find({session: session._id}, function(err, exercise) {
let movements = [];
let sets = [];
let i = exercise.length-1;
//For every exercise get the movements and the sets
exercise.forEach(function (ex,index) {
Movement.findById(ex.movement,function(err,movement){
if(movement)
movements.push(movement);
//***** Here?
Set.find({exercise: ex}, function (err, set) {
if(set.length)
sets.push(set);
if(index == i){
res.json({ message: 'ok' ,session,exercise,movements,sets});
}
})
})
})
});
});
})
The idea is obtain all the session related information from the database.
First:
I think that is not the correct way of make multiple querys and return an object with the info of all the querys, but I'm novice with the async working of Node... So what is the correct way to make multiple querys where the data of one query depends of other query?
Second: In the Front-End (React + Redux) I'm making Ajax request with axios and for the same Ajax request sometimes not all 'sets' are fetched (//***** Here?). The problem is in the API?
Thanks in advance.
EDIT: DB models
Session:
var SessionSchema = new Schema({
date: {type: Date, default: Date.now },
time: Number, //Time in seconds
user: {required: true, type: mongoose.Schema.Types.ObjectId, ref: 'User'},
});
Exercise:
var ExerciseSchema = new Schema({
session: {type: mongoose.Schema.Types.ObjectId, ref: 'Session'},
movement: {type: mongoose.Schema.Types.ObjectId, ref: 'Movement'},
timestamp: {
type: Date,
default: Date.now
}
});
Set:
var SetSchema = new Schema({
repetitions: Number,
weight: Number,
rest: Number,
timestamp: {
type: Date,
default: Date.now
},
exercise : {type: mongoose.Schema.Types.ObjectId, ref: 'Exercise'}
});
Movement:
var MovementSchema = new Schema({
name: { type: String, required: true, index: true, unique: true },
material:{ type: String, required: true},
muscles : [{
name : { type: String, required: true},
percentage : { type: Number, required: true}
}]
});
Set.find({exercise: ex}, function (err, set) {
if(set.length)
sets.push(set);
}).then(function(set){
if(index == i){
res.json({ message: 'ok' ,session,exercise,movements,sets});
}
})
Of course my prev answer wouldn't work. The set query callback would execute after the if(index == i). Actually I'm not sure this will produce different results from your code. I've never actually used Mongoose but as far as I know, you can't do joins so nested queries is the way to do it.
You might want to consider using promises. Not necessary, but they make your code easier to read and think about: http://eddywashere.com/blog/switching-out-callbacks-with-promises-in-mongoose/
It might make more sense as well to create a single result object that you build up as the queries return so you end up sending a single JSON object representing your session that looks like:
{
exercises: [
{
sets: [],
movements: [],
},
{
sets: [],
movements: [],
},
...
]
}
I try to use .findOneAndUpdate() to update my database.
No error message, but this part of the database is not updated with new data. The embedded document competitorAnalysisTextData is still empty.
// on routes that end in /users/competitorAnalysisTextData
// ----------------------------------------------------
router.route('/users/competitorAnalysisTextData/:userName')
// update the user info (accessed at PUT http://localhost:8080/api/users/competitorAnalysisTextData)
.post(function(req, res) {
console.log('1');
// Just give instruction to mongodb to find document, change it;
// then finally after mongodb is done, return the result/error as callback.
User.findOneAndUpdate(
{ userName : req.params.userName},
{
$set:
{ "competitorAnalysis.firstObservation" : req.body.firstObservation,
"competitorAnalysis.secondObservation" : req.body.secondObservation,
"competitorAnalysis.thirdObservation" : req.body.thirdObservation,
"competitorAnalysis.brandName" : req.body.brandName,
"competitorAnalysis.productCategory" : req.body.productCategory
}
},
{ upsert: true },
function(err, user) {
// after mongodb is done updating, you are receiving the updated file as callback
console.log('2');
// now you can send the error or updated file to client
if (err)
return res.send(err);
return res.json({ message: 'User updated!' });
});
})
Update
This is my "User" Schema part:
// grab the things we need
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// Require the crypto module for password hash
'use strict';
var crypto = require('crypto');
// create competitorAnalysisSchema
var CompetitorAnalysis = new Schema({
firstObservation: { type: String },
secondObservation: { type: String },
thirdObservation: { type: String },
brandName: { type: String },
productCategory: { type: String }
});
// create competitorAnalysisPhotoSchema
var CompetitorAnalysisPhoto = new Schema({
photo1: {type: String},
photo2: {type: String},
photo3: {type: String},
photo4: {type: String}
});
// create UserSchema
var UserSchema = new Schema({
userName: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
currentDemo: { type: String },
nextDemo: { type: String },
startTime: { type: String },
startLocation: { type: String },
arriveTime: { type: String },
arriveLocation: { type: String },
leaveTime: { type: String },
leaveLocation: { type: String },
competitorAnalysis: [CompetitorAnalysis],
competitorAnalysisPhoto: [CompetitorAnalysisPhoto],
created_at: Date,
updated_at: Date
});
// the schema is useless so far
// we need to create a model using it
var User = mongoose.model('User', UserSchema);
// make this available to our users in our Node applications
module.exports = User;
in javascript if you wish to update an object inside an array, you need to pick the index
var arr = [{name: "person1"},{name:"person2"}]
arr[0].name = "myname"
arr[1].name = "myFriend"
So it's the same in mongodb, check this link for detail example, or you can manually input the index, for quick hack.
User.findOneAndUpdate(
{ userName : req.params.userName},
{
$set:
{ "competitorAnalysis.0.firstObservation" : req.body.firstObservation,
"competitorAnalysis.0.secondObservation" : req.body.secondObservation,
"competitorAnalysis.0.thirdObservation" : req.body.thirdObservation,
"competitorAnalysis.0.brandName" : req.body.brandName,
"competitorAnalysis.0.productCategory" : req.body.productCategory
}
},
{ upsert: true },
function(err, user) {
// after mongodb is done updating, you are receiving the updated file as callback
console.log('2');
// now you can send the error or updated file to client
if (err)
return res.send(err);
return res.json({ message: 'User updated!' });
});
})
You should use the code above to update nested-array not to add to empty-array.
In javascript, if an array is still empty, we use .push() to add, while in mongodb the command is $push
var arr = []
arr.push({name:"person1"})
When do models receive their prototype?
I know that embedding is generally the answer here, but I have a special case.
If I call to another model in an instance's custom method, it seems to fail.
The error I'm getting:
Fish.find is not a function at model.UserSchema.methods.fishes
The Fish model is made into a model:
// Require mongoose to create a model.
var mongoose = require('mongoose'),
User = require('./user.js');
// Create a schema of your model
var fishSchema = new mongoose.Schema({
name: String,
category: String,
user: { type: mongoose.Schema.Types.ObjectId, ref:'User' }
});
// Create the model using your schema.
var Fish = mongoose.model('Fish', fishSchema);
// Export the model of the Fish.
module.exports = Fish;
The User model calls to the Fish model within the fishes custom instance method:
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
bcrypt = require('bcrypt-nodejs'),
Fish = require('./fish');
//||||||||||||||||||||||||||--
// CREATE USER SCHEMA
//||||||||||||||||||||||||||--
var UserSchema = new Schema({
name: { type: String, required: true },
phoneNumber: {
type: String,
required: true,
index: { unique: true },
minlength: 7,
maxlength: 10
},
password: { type: String, required: true, select: false }
});
// … some bcrypt stuff…
// Access user's fishes - THIS IS WHAT'S MESSING UP!!
UserSchema.methods.fishes = function(callback) {
Fish.find({user: this._id}, function(err, fishes) {
callback(err, fishes);
});
};
module.exports = mongoose.model('User', UserSchema);
When I call .fishes() in my seeds, it claims that Fish.find is not a function.
Why!? Any help would be greatly appreciated!
The issue is a circular import (fish.js requires user.js that requires fish.js, etc).
You can work around that by resolving the model class at runtime:
UserSchema.methods.fishes = function(callback) {
mongoose.model('Fish').find({user: this._id}, function(err, fishes) {
callback(err, fishes);
});
};
I am currently trying to learn how to work with NoSQL, coming from a relational database background. In this project, I am using Express with Mongoose.
I am struggling with callbacks as I try to merge two models together, which reference each other. I am trying to edit each item in a group of one model (Ribbits) to contain the attributes of another (Users who posted a Ribbit). Because the call to find the User associated with a Ribbit is asynchronous, I am unable to return the collection of edited Ribbits (with user info).
In my website, I have ribbits (a.k.a. tweets) which belong to users. Users can have many ribbits. In one of my pages, I would like to list all of the ribbits on the service, and some information associated with the user who posted that ribbit.
One solution I found was embedded documents, but I discovered that this is, in my case, limited to showing ribbits which belong to a user. In my case, I want to start by getting all of the ribbits first, and then, for each ribbit, attach info about who posted that.
Ideally, I'd want my schema function to return an array of Ribbit objects, so that I can then render this in my view.
// models/user.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var userSchema = Schema({
username: String,
email: String,
password: String,
name: String,
profile: String,
ribbits: [{
type: Schema.Types.ObjectId,
ref: 'Ribbit',
}]
});
module.exports = mongoose.model('User', userSchema);
// models/ribbit.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
User = require('./user');
var ribbitSchema = Schema({
content: { type: String, maxlength: 140 },
created: { type: Date, default: Date.now() },
owner: { type: Schema.Types.ObjectId, ref: 'User' },
});
ribbitSchema.methods.getOwnerObj = function(cb) {
return User.findOne({ _id: this.owner }, cb);
}
ribbitSchema.statics.getAllRibbits = function(cb) {
this.find({}, function(err, ribbits) {
console.log('Before Transform');
console.log(ribbits);
ribbits.forEach(function(ribbit) {
ribbit.getOwnerObj(function(err, owner) {
ribbit = {
content: ribbit.content,
created: ribbit.created,
owner: {
username: owner.username,
email: owner.email,
name: owner.name,
profile: owner.profile,
}
};
});
});
});
}
module.exports = mongoose.model('Ribbit', ribbitSchema);
If I understand correctly, you can use Mongoose populate method for this scenario:
ribbitSchema.statics.getAllRibbits = function(cb) {
this.find({}).populate('owner').exec(function(err, ribbits){
console.log(ribbits[0].owner)
return cb(err, ribbits);
})
}
I'm trying to implement this solution and I'm not sure where to put it. I see the db variable called frequently, but I'm still new to node and mongoDb, so I don't know how to call it in my Model. Here is the syntax to ensure an index spanning multiple fields...
db.collection.ensureIndex( {
description: "text",
title: "text"
} );
Here is my model...
// Module dependencies.
var mongoose = require('mongoose'),
config = require('../../config/config'),
Schema = mongoose.Schema,
findOrCreate = require('mongoose-findorcreate'),
textSearch = require('mongoose-text-search');
// Product Schema
var ProductSchema = new Schema({
created: {
type: Date,
default: Date.now
},
retailer: {
type: String,
required: true,
trim: true
},
retailer_category: {
type: String,
required: true,
trim: true
},
product_id: {
type: String,
required: true,
trim: true
},
link: {
type: String,
trim: true
},
title: {
type: String,
trim: true
},
price: {
type: Number
},
// Rating - 0 out of 5 (can be decimal)
rating: {
type: Number
},
description: {
type: String,
trim: true
},
variations: {
type: Schema.Types.Mixed,
default: []
},
images: {
type: Boolean,
default: false
}
});
// Validations
ProductSchema.index({ retailer: 1, product_id: 1 }, { unique: true });
// Statics
ProductSchema.statics = {
load: function(id, cb) {
this.findOne({
_id: id
}).exec(cb);
}
};
// Plug-Ins
ProductSchema.plugin(findOrCreate);
ProductSchema.plugin(textSearch);
mongoose.model('Product', ProductSchema);
var Product = mongoose.model('Product', ProductSchema);
Product.ensureIndexes( function(err) {
if (err) {
console.log(err);
}
})
It's worth noting:
When your application starts up, Mongoose automatically calls ensureIndex for each defined index in your schema. While nice for development, it is recommended this behavior be disabled in production since index creation can cause a significant performance impact. Disable the behavior by setting the autoIndex option of your schema to false.
from http://mongoosejs.com/docs/guide.html
I scratched my head over this one too. After digging around the mongoose test cases, I found that ensureIndex resides in a mongoose model's collection property.
var ProductModel = mongoose.model('Product', ProductSchema);
ProductModel.collection.ensureIndex({
description : 'text',
title : 'text'
}, function(error, res) {
if(error){
return console.error('failed ensureIndex with error', error);
}
console.log('ensureIndex succeeded with response', res);
});
Note that a callback is required, or Mongo will throw the error:
Error: Cannot use a writeConcern without a provided callback