How to access model attributes of different controller in Sails.js? - sails.js

I have two controllers/models in my Sails project which is Clubs and Members. One club can have many members.
I try to put the id of 'Clubs' as a reference id (like a foreign key) in 'Members', so that I can retrieve the members of a club by using the reference id in 'Members'. I want to display the members according to their clubs at the homepage. However I could not find a way to pass the id value of 'Clubs' to the 'Members' controller. Below are some of the codes:
Clubs.js
module.exports = {
attributes: {
clubName: {
type: 'string',
},
clubDesc: {
type: 'string',
},
},
};
Members.js
module.exports = {
attributes: {
memberName: {
type: 'string',
},
clubId: {
type: 'string',
},
},
};
ClubsController.js
module.exports = {
list: function(req, res) {
Clubs.find({}).exec(function(err, club) {
if(err) {
res.send(500, {error: 'Database Error'});
}
res.view('pages/club-list', {clubs:club});
});
},
add: function(req, res) {
res.view('pages/club-add');
},
create: function(req, res) {
var clubName = req.body.clubName;
var clubDesc = req.body.clubDesc;
Clubs.create({clubName:clubName, clubDesc:clubDesc}).exec(function(err){
if(err) {
res.send(500, {error: 'Database Error'});
}
res.redirect('/clubs/list');
});
},
};
MembersController.js
module.exports = {
list: function(req, res) {
Members.find({}).exec(function(err, member) {
if(err) {
res.send(500, {error: 'Database Error'});
}
res.view('pages/member-list', {members:member});
});
},
add: function(req, res) {
res.view('pages/member-add');
},
create: function(req, res) {
var memberName = req.body.memberName;
var clubId = req.body.clubId;
Members.create({memberName:memberName,
clubId:clubId}).exec(function(err){
if(err) {
res.send(500, {error: 'Database Error'});
}
res.redirect('/members/list');
});
},
};
routes.js
module.exports.routes = {
'/': {
view: 'pages/homepage',
},
'/clubs/list': {
view: 'pages/club-list',
controller: 'Clubs',
action: 'list'
},
'/clubs/add': {
view: 'pages/club-add',
controller: 'Clubs',
action: 'add'
},
'/clubs/create': {
controller: 'Clubs',
action: 'create',
},
'/members/list': {
view: 'pages/member-list',
controller: 'Members',
action: 'list'
},
'/members/add': {
view: 'pages/member-add',
controller: 'Members',
action: 'add'
},
'/members/create': {
controller: 'Members',
action: 'create',
},
};
I'm really new to Sails.js here and I find that it's quite difficult to get resources on this matter. I'm not sure if I put this in a way that you guys could understand. But do ask for more details if you guys need more understanding. Thank you in advance.

If I understand correctly, you're looking to create a one-to-many association between Clubs and Members. Here's how it should look in Clubs.js, your 'many':
attributes: {
...
members: {
collection: 'Members',
via: 'club'
}
}
Then in Members.js, your 'many':
attributes: {
...
club: {
model: 'Clubs'
}
}
When you do Club.find(), the members key will be an array of member ids. If you do Club.find().populate('member'), the members key will be an array of fully-populated member objects.
Here are the docs on associations.
This isn't directly related to your question, buy since you are new to Sails, I am including a comment that will give you some advice on how to best use the framework. I hope it goes well!

Related

MongoDB query won't return object in my Express API (React)

I have done this so many times before, but I can't seem to find the issue, it's probably something small and stupid. Take a look at the /server.js file here! (Shortened for demonstration purposes)
/* Make Mongoose promise based */
mongoose.Promise = Promise;
mongoose.connect('mongodb://localhost:27017', options);
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error: '));
/* Routes */
app.route('/games')
.post(postGame)
.get(getGames);
app.route('/games/:id')
.get(getGame)
.delete(deleteGame);
app.route("*").get((req, res) => {
res.sendFile('client/dist/index.html', { root: __dirname });
});
const port = 8080;
app.listen(port, () => {
console.log(`Connected! Server listening on port: ${port}`);
});
Then for my Game model, I have that in app/models/game.js.
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const gameSchema = new Schema(
{
name: {
type: String,
required:true
},
year: {
type: Number,
required:true
},
description: {
type: String,
required:true
},
picture: {
type: String,
required:true
},
postDate : { type: Date, default: Date.now }
}
);
export default mongoose.model('Game', gameSchema);
This is where I believe I am having the issue.
/* Import Game model schema */
import Game from '../models/game';
const getGames = (req, res) => {
Game.find({}, (err, games) => {
console.log(err, games)
if (err) {
res.send(err);
}
res.json(games);
});
}
const getGame = (req, res) => {
const { id } = req.params;
Game.findById(id, (err, game) => {
if (err) {
res.send(err);
}
res.json(game);
});
}
const postGame = (req, res) => {
let game = Object.assign(new Game(), req.body);
game.save(err => {
if (err) {
res.send(err);
}
res.json({ message: 'Game successfully created!' });
});
};
const deleteGame = (req, res) => {
Game.remove(
{ _id: req.params.id },
err => {
if (err) {
res.send(err);
}
res.json({ message: 'Game successfully deleted!' });
}
);
};
export {
getGames,
getGame,
postGame,
deleteGame
};
Just do be clear... I went into the mongo shell.
I did...
connecting to: test
> db.createCollection('Game')
> db.Game.insert({name: "SSB", year: 2001, description: "Fun Game", picture: "http://google.com", postDate: "2017-01-03T08:51:45.888Z"});
And when I type > db.Game.find({}); I am returned with exactly what I have...
{
"_id" : ObjectId("58c2223e32daa04353e35bdc"),
"name" : "SSB",
"year" : 2001,
"description" : "Fun Game",
"picture" : "http://google.com",
"postDate" : "2017-01-03T08:51:45.888Z"
}
You see when I go to http://localhost:8080/games I am returned with an empty JSON and I just wanna know why. I am 70% sure, it is because it isn't connected to the right collection but I don't remember how to test that :(
I wanted to make this a comment but it won't let me because I don't have a 50 reputation, but I believe I found the issue.
const getGame = (req, res) => {
const { id } = req.params;
Game.findById(id, (err, game) => {
if (err) {
res.send(err);
}
res.json(game);
});
}
In this piece of code you are setting the id to req.params, but you need to set it to req.params.id which is what you passed in your route.
Should look like this:
const {id} = req.params.id;
If you logged id, you would probably get an object that says:
{ id: "[whatever_id_you_put_here]" }
however if you log req.params.id you should get the correct id you put in that spot..
The reason you're getting [] is because you're actually connected to the database and you are actually trying to "get" something, but that something doesn't exist so it sends an empty response.
I hope this helps..

How to make querys when tou have many to many relationships between models?

i am trying to make a game. I need tu create a Match. I think the problem on this Way. The User create a Match. In a third table I save playerId and gameId. When another user join the match, I save again, playerId and gameId. Then, I make a query with player with gameId in common, and start the game.
first, One User may have many Games. second, One Match may have many Games. this is the Match model:
module.exports = {
attributes: {
name: {
type: 'string'
},
description: {
type: 'string'
},
game: {
collection: 'game',
via: 'gameId',
}
}
};
This is the User model:
var bcrypt = require('bcrypt');
module.exports = {
attributes: {
name: {
type:'string'
},
email: {
type: 'email',
required: true,
unique: true
},
password: {
type: 'string',
},
passwordConfirmation: {
type: 'string'
},
passwordEncrypted: {
type: 'string'
},
creator: {
collection: 'game',
via: 'playerId'
},
toJSON: function(){
var obj = this.toObject();
delete obj.password;
delete obj.passwordConfirmation;
delete obj._csrf;
return obj;
}
}, beforeCreate: function(values, next){
console.log("Acabo de entrar a eforeCreate");
var password = values.password;
var passwordConfirmation = values.passwordConfirmation;
if(!password || !passwordConfirmation || password != values.passwordConfirmation) {
var passwordDoesNotMatchError = [{
name: 'passwordDoesNotMatchError',
message: 'Las contraseñas deben coincidir'
}]
return next({
err: passwordDoesNotMatchError
});
}
require('bcrypt').hash(values.password, 10, function passwordEncrypted(err, EncryptedPassword){
values.EncryptedPassword = EncryptedPassword;
next();
});
}
};
This is the Game model:
module.exports = {
attributes: {
gameId: {
model: 'match'
},
playerId: {
model: 'user'
}
}
};
finally, this is my controller:
module.exports = {
createMatch: function(req,res){
var matchObj = {
name: req.param('name'),
description: req.param('description'),
}
Match.create(matchObj, function(err, match){
if(err){
console.log("el error fue: " + err);
return res.send(err);
} console.log("Entro en create");
return res.json(match);
})
var gameObj = {
gameId: 'aclaration: I dont know how do I get the match.id',
playerId: req.session.me
}
Game.create(gameObj,function(err,game){
console.log("entro a GameCreate");
if(err){
return res.send(err);
} return res.json(game);
})
}
};
I can create the Match, but Game.create send this error:
_http_outgoing.js:344 throw new Error('Can\'t set headers after they are sent.'); ^
Error: Can't set headers after they are sent.
Somebody can help me? probably, I have many errors. Thanks.
Couple of things here:
Having an explicit Game model is not required in Sails. It can manage it implicitly, unless you want to store more information than just gameId and userId. So, you can just do away with Game model.
Please refer for async programming: How do I return the response from an asynchronous call?
Below code should work for you. Hope it helps.
module.exports = {
createMatch: function(req, res) {
var matchObj = {
name: req.param('name'),
description: req.param('description'),
};
Match.create(matchObj, function(err, match) {
if (err) {
console.log("el error fue: " + err);
return res.send(err);
}
console.log("Entro en create");
var gameObj = {
gameId: match.id,
playerId: req.session.me
};
Game.create(gameObj, function(err, game) {
console.log("entro a GameCreate");
if (err) {
return res.send(err);
}
return res.json(game);
// return res.json(match);
});
});
}
};

Multiple chained promises is sailsjs

This is my first attempt at attempting to chain multiple finds together. The debug running shows that all the code executes correctly but there is a delay in receiving the users array back and therefore unable to present the data back.
The concept is a user may belong to multiple organizations, and there may be more than one user (other than the current user) that may belong to organizations. The function is trying to receive all users for all the organizations the current user belongs to.
getUserOrganizationsUsers: function (userId) {
var users = [];
sails.log.info('Getting the current users organizations [' + userId + ']');
return UserOrganization.find({ user_id: userId, deleted: null })
.populate('organization_id', { deleted: null })
.populate('user_id', { deleted: null })
.then(function (userorganization) {
return userorganization;
})
.then(function (userorgs) {
/* From all the organizations I want to get all the users from those organizations */
_.forEach(userorgs, function (userorg) {
UserOrganization.find({ organization_id: userorg.organization_id.id })
.populate('organization_id', { deleted: null })
.populate('user_id', { deleted: null })
.then(function (otherusrs) {
_.forEach(otherusrs, function (otherusr) {
sails.log.info('other userss each loop ');
var users = _.find(otherusrs, {id: otherusr.organization_id.id});
users.push(users);
})
})
});
return Q.when(employees);
})
},
Organization.js
module.exports = {
attributes: {
companyName: {
type: 'string',
required: true
},
Address: {
type: 'string'
},
ABN: {
type: 'string'
},
City: {
type: 'string'
},
contactNumber: {
type: 'string'
},
country: {
type: 'string'
},
icon: {
type: 'string'
},
users:
{ collection: 'userorganization',
via : 'user_id'
},
deleted: {
type: 'date',
defaultsTo: null
},
toJSON: function () {
var obj = this.toObject();
obj = _.pick(obj, Organization.publicFields);
return obj;
}
},
editableFields: [
'companyName',
'users'
// 'industries'
],
publicFields: [
'id',
'companyName',
'users'
],
};
UserOrganization.js
module.exports = {
attributes: {
organization_id: {
model : 'organization',
required: true
},
user_id: {
model: 'user',
required: true
},
organizationRole: {
type: 'string',
required: true
},
deleted: {
type: 'date',
defaultsTo: null
},
toJSON: function () {
var obj = this.toObject();
obj = _.pick(obj, UserOrganization.publicFields);
return obj;
}
},
editableFields: [
'organization_id',
'user_id',
'organizationRole',
],
publicFields: [
'id',
'organization_id',
'user_id',
'organizationRole'
],
};
and the user.js
var bcrypt = require('bcrypt-nodejs');
module.exports = {
attributes: {
email: {
type: 'email',
required: true,
unique: true
},
password: {
type: 'string',
required: true
},
firstName: {
type: 'string'
},
lastName: {
type: 'string'
},
verified: {
type: 'boolean',
defaultsTo: false
},
organizations:
{ collection: 'userorganization',
via : 'user_id'
}, deleted: {
type: 'date',
defaultsTo: null
},
fullName: function () {
return this.firstName + ' ' + this.lastName;
},
toJSON: function () {
var obj = this.toObject();
obj = _.pick(obj, User.publicFields);
return obj;
}
},
// TODO: Add initialFields
editableFields: [
'password',
'email',
'firstName',
'lastName',
'organizations'],
publicFields: [
'id',
'email',
'verified',
'firstName',
'lastName',
'fullName',
'organizations'
],
comparePassword: function (password, user, cb) {
bcrypt.compare(password, user.password, function (err, match) {
if(err) return cb(err);
cb(null, match);
})
},
beforeCreate: function (user, cb) {
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(user.password, salt, function () {}, function (err, hash) {
if (err) {
sails.log.error(err);
return cb(err);
}
user.password = hash;
cb(null, user);
});
});
}
};
Okay, I think I understand what you're doing. It would be a lot simpler to have the User belong to an organization directly.
Anyways, if I understood your model structure correctly, something like this should work:
getUserOrganizationsUsers: function (userId) {
UserOrganization.find({ user_id: userId, deleted: null })
.then(function (userOrgs) {
// return array of organization IDs
return _.map(userOrgs, function(org){
return org.id;
});
})
.then(function (userOrgs) {
Organization.find(userOrgs)
.populate('users') // users is a collection of UserOrganization
.exec(function(err, orgs){ // lookup organizations
if(err) //handle error
else {
return _.flatten( // return basic array for next promise handler
_.map(orgs, function(org){ // for each organization
return _.map(org.users, function(user){ // return an array of user_ids
return user.user_id;
})
})
)
}
})
})
.then(function(allUserOrgs){
UserOrganization.find(allUserOrgs)
.populate('user_id')
.exec(function(err, userOrgsList){
return _.map(userOrgsList, function(user){
return user.user_id;
})
})
})
.then(function(users){
// users should be an array of all the users form allt he organizations that the current users belongs to
})
},

Sails.js: cannot do POST for inserting into mongoDB

I am trying to write a toy program for submitting form and inserting into mongodb, but I keep getting DB error. Let me paste the relevant code here, and I hope to get some help.
I am using Sails ver 0.10.5 and the latest mongo 2.6.5, and my I run node on my Mac OSX 10.9:
Model: Employee.js
module.exports = {
attributes: {
name: {
type: "string",
required: true
},
email: {
type: "string",
required: true
},
password: {
type: "string",
required: true
}
},
beforeCreate: function(values, next) {
next();
}
};
route.js:
module.exports.routes = {
'/registeremployee': {
controller: 'employee',
action: 'register'
},
'/listemployees': {
controller: 'employee',
action: 'list_all'
}
};
EmployeeController.js
module.exports = {
index: function(req, res) {
res.send(200, {title: "employee index page"});
},
list_all: function(req, res) {
Employee.find().exec(function(err, employee) {
if (err) {
res.send(500, {title: 'error retrieving users'});
} else {
res.send(200, {'employees': employee});
}
});
},
register: function(req, res) {
if (req.method == "GET") {
res.view({title: "Form for registering employees"});
} else if (req.method == "POST") {
var username = req.param("username");
var password = req.param("password");
var email = req.param("email");
console.log("saving the username: " + username); //username printed as 'undefined'
Employee.create({username: username, password: password, email: email}).exec(function(error, employee) {
if (error) {
console.log('error');
res.send(500, {error: "DB error!"});
} else {
console.log('error');
res.send(200, employee);
console.log("saved employee: " + employee.username);
}
});
}
}
};
Lastly, the register.ejs template file:
List All Users
<h2>Form - Create a User</h2>
<form action="/registeremployee" method="POST">
<table>
<tr><td>Name</td><td><input type=”text” name=”username” required></td></tr>
<tr><td>Password</td><td><input type=”password” name=”password” required></td></tr>
<tr><td>Email</td><td><input type=”email” name=”email” required></td></tr>
<tr><td></td><td><input type="submit"></td>
</table>
</form>
It looks to me that the form does not submit data, as the parameters are printed as undefined/null in my controller.
I have this in my connections.js:
mongo: {
adapter: 'sails-mongo',
host: 'localhost',
port: 27017,
user: '',
password: '',
database: 'sailsApp1'
}
just replace name to username and you are able to save data in db.
module.exports = {
attributes: {
username: {
type: "string", required: true },
email: { type: "string", required: true },
password: { type: "string", required: true } },
beforeCreate: function(values, next) { next(); } };

Sails.js 0.10.0-rc5 many-to-many association: remove

i'm developing an app with sails.js beta and mongodb.
I've two models in a many-to-many association, i can successfully associate and populate instances of these models using .add() and .populate() methods. My problem is now that the .remove() method seems to do nothing.
here the models:
//Menu.js
module.exports = {
schema : true,
attributes: {
name: {
type: 'string',
minLength: 3,
required: true
},
dishes: {
collection: 'dish',
via: 'menus',
dominant: true
}
}
};
//Dish.js
module.exports = {
schema : true,
attributes: {
name: {
type: 'string',
minLength: 3,
required: true
},
description: 'string',
menus: {
collection: 'menu',
via: 'dishes'
}
}
};
And here the controller actions...
addDishToMenu: function(req,res,next){
Menu.findOne(req.param('menu')).populate('dishes').exec(function(err,bean){
if(err) return next(err);
if(!bean) return next();
bean.dishes.add(req.param('dish'));
bean.save(function(err) {
if(err) return next(err);
res.redirect('/main/dishes/');
})
})
},
removeDishFromMenu: function(req,res,next){
Menu.findOne(req.param('menu')).populate('dishes').exec(function(err,bean){
if(err) return next(err);
if(!bean) return next();
bean.dishes.remove(req.param('dish'));
bean.save(function(err) {
if(err) return next(err);
res.redirect('/main/menu/' + req.param('menu'));
})
})
}
I can't figure out what i'm doing wrong. Any ideas?
This issue has been fixed and I confirmed it with the repo I sent earlier. If you update your sails, waterline, and sails-mongo versions you should be good to go.