Updating association in Sails - sails.js

I am using SailsJs. Two models exist. Model 'person' contains the following attribute:
books: {
collection: 'book',
via: 'owner'
},
Model 'book' contains:
owner: {
model: 'person'
},
When I create an instance of person, I can use http request and simply put something like the following
Post /person?books=1001,1002
As long as 1001 and 1002 are valid ids of book, it works.
However, when I try to person's books attribute, this does not work
Post /person/1?books=1002,1003
books of person with id 1 becomes empty.
But,
Post /person/1?books=1002
would work.
Why is this? How can I modify collection attribute?

There are 2 options to update a model using the blueprints API:
Send the data as the body of the post request. send the data as a json object in the request body
Post /person/1
body: { "books": [1002,1003]}
Override the blueprint update method
Add this the PersonController.js:
update: function (req, res) {
Person.findOne(req.param('id')).populate('books').exec(function (err, person){
if (err) {
sails.log.error(err);
return;
}
var books = req.param('books').split(',').map(function (book){return parseInt(book);}),
booksToRemove = _.difference(person.books.map(function(book) {return book.id;}), books);
// Remove books that are not in the requested list
person.books.remove(booksToRemove);
// Add new books to the list
person.books.add(books);
person.save(function (err, r) {;
res.json(r);
});
});
}

Related

How to populate with a second model if first one is empty

I've three models,
modelA, modelB and ModelC
ModelC's data is here
{
"_id" : ObjectId("586e1661d9903c6027a3b47c"),
"RefModel" : "modelA",
"RefId" : ObjectId("57f37f18517f72bc09ee7632")
},
{
"_id" : ObjectId("586e1661d9903c6027a3b47c"),
"RefModel" : "modelB",
"RefId" : ObjectId("57f37f18517f72bc09ee7698")
},
Howto write a populate query for ModelC, to populate with RefId .
it should populate with modelA or modelB which RefModel refers.
I've tried with
ModelC.find({})
.populate({ path: 'RefId', model: 'modelA' })
.populate({ path: 'RefId', model: 'modelB' })
But taking only the last model.
modelC schema.
new mongoose.Schema({
RefModel: String,
RefId:{ type: Schema.ObjectId}});
I could do it with aggregate, but preferring populate.
Fields' names in your database and schema are very confusing, let me explain on more clear example.
Suppose you have 3 models: User, Article and Comment. Article and Comment belongs only to single User. User can have multiple comments and articles (As you shown in your example).
(More efficient and reccommending way). Store comments and articles ids in User model like:
comments: [{ id: '..', ref: Comment }],
articles: [{ id: '..', ref: Article }]
and populate your document:
User.find({})
.populate('comments')
.populate('articles');
Store User id in Comment and Article models like
user: { id: '...', ref: User }
and use mongoose-reverse-populate module for population, for example for comments model:
var reversePopulate = require('mongoose-reverse-populate');
User.find().exec(function(err, users) {
var opts = {
modelArray: users,
storeWhere: "comments",
arrayPop: true,
mongooseModel: Comments,
idField: "user"
}
reversePopulate(opts, function(err, popUsers) {
// populated users will be populated with comments under .comments property
});
});
Also you dont need to keep RefModel in your database, only in schema.
Here, you can try and populate it inside the callback of find itself.
Try this:
ModelC.find({}).exec(function(err,modelC){
//find returns an array of Object, you will have to populate each Object individually.
var modelCObjects =[];
modelC.forEach(function(tempModelC){
//populate via the Model
ModelC.populate(tempModelC,{path : tempModelC.refId , model :tempModelC.refModel},function(err2,newModelC){
//handle newModelC however you want
//Push it into array of modelC Objects
modelCObjects.push(newModelC);
});
//OR
//Populate directly on the resultObject <-im not so sure about this
tempModelC.populate({path : tempModelC.refId , model :tempModelC.refModel},function(err2,newModelC){
//Push it into array of modelC Objects
modelCObjects.push(newModelC);
})
});
//here you have modelCObjects -> Array of populated modelC Objects
});
Please make sure to handle the errors.

How to update (override) a collection association's value in sails.js?

I have a model that has an attribute that is a collection association:
Take for example, a User model below.
module.exports = {
attributes: {
pets: {
collection: 'pet'
}
}
}
I am aware that I can add pets to a user instance with
user.pets.add(3);
But how could I replace any existing pets with a new group of pets??
Ok I've been playing with the API and found an answer. The following call should update (set) the pets association for a single user. If there were existing pets, this approach would override them.
User.update({id:1}, {pets: [{id: 7}, {id: 8}]}).exec(cb);
You'd remove all the existing pets and create new ones. sails.js has no single special API function to do what you are trying to do, but it's pretty simple either way:
var newPets = [
{ name: 'fluffy', user: 1 },
...
];
Pet.destroy({ user: 1 })
.then(function () {
return _.map(newPets, Pet.create);
})
.then(function (pets) {
// pets are "replaced"
});
Or something like that.

Sails.js - Many-to-Many fails to save

I have a simple need to add tags to patients. I followed the Sails and Waterline documentation concerning many-to-many associations, but it's failing at some point (no errors). I'm using MongoDB for data storage. Code below:
Tag Model
module.exports = {
attributes: {
name: 'STRING',
color: {
type: 'STRING',
defaultsTo: '#777777'
},
tagged: {
collection: 'patient',
via: 'tags',
dominant: true
}
}
};
Patient Model
module.exports = {
attributes: {
name: 'STRING',
tags: {
collection: 'tag',
via: 'tagged'
}
}
};
And this is the controller method that tries to associate data:
module.exports = {
addToPatient: function(req, res) {
Patient.findOne({id: req.param('patientId')}).exec(function(err, patient) {
// Queue up a record to be inserted into the join table
patient.tags.add(req.param('tagId'));
// Save the user, creating the new associations in the join table
patient.save(function(err) {});
});
res.send("tag assigned");
}
};
I've inspected the responses at various breaks and everything seems to be passing just fine. The patient is found. The save function shows a tag association in the patient object, but nothing is added in the database. I assume I will see either a join table being created or something in the patient/tag collections to signify an association, but I see nothing. I'm so very confused. If I do an HTTP get, I'm presented with a "tag assigned" response. What am I missing?
Works fine for me, but you're right in the tag and patient collections, you won't see a populated field with the associations. You'll see new join collections that are created that contains the relationships like #sgress454 pointed out.

Why does Sails v.0.10.x respond to POST to nested route with one-to-many association?

In my app, I have two models Person and Room. I also have two controllers, PersonController and RoomController. I have added no custom routes, and I've overridden none of the boilerplate controller actions.
When I POST to, for example, /room/5/person, a person record is created and associated with room/5 as expected - great. However, the JSON returned in the response to this POST contains the Room and all People associated with the room, side-loaded in an array. I expected that Sails would return only the person created (or, at most, the newly created person and the room, if blueprints.populate: true).
What changes to I need to make to so that Sails returns only the Person created as a result of the POST?
Here's is my current code:
/api/models/Room.js
// Room.js
module.exports = {
autosubscribe: ['update', 'add:people'],
attributes: {
name: 'string',
// Has-many association
// A room can have many people
people: {
collection: 'person',
via: 'room'
}
}
};
/api/models/Person.js
module.exports = {
autosubscribe: ['update'],
attributes: {
name: 'string',
// Belongs-to association
// A person can belong to only one room
room: {
model: 'room'
}
}
};
/api/controllers/RoomController.js
module.exports = {
};
/api/controllers/PersonController.js
module.exports = {
};
/config/routes.js
module.exports.routes = {
'/': {
view: 'homepage'
}
};
This is by design; you're using the "add person to room" blueprint, so it's assumed you care about the state of the room. If you just want the person returned, use the "create person" blueprint, and supply the room ID in the payload:
POST /person
{
name: "John Doe",
room: 5
}
This will still send out pubsub notifications about a person being added to room 5.

Sailsjs and Associations

Getting into sails.js - enjoying the cleanliness of models, routes, and the recent addition of associations. My dilemma:
I have Users, and Groups. There is a many-many relationship between the two.
var User = {
attributes: {
username: 'string',
groups: {
collection: 'group',
via: 'users'
}
}
};
module.exports = User;
...
var Group = {
attributes: {
name: 'string',
users: {
collection: 'user',
via: 'groups',
dominant: true
}
}
};
module.exports = Group;
I'm having difficulty understanding how I would save a user and it's associated groups.
Can I access the 'join table' directly?
From an ajax call, how should I be sending in the list of group ids to my controller?
If via REST URL, is this already accounted for in blueprint functions via update?
If so - what does the URL look like? /user/update/1?groups=1,2,3 ?
Is all of this just not supported yet? Any insight is helpful, thanks.
Documentation for these blueprints is forthcoming, but to link two records that have a many-to-many association, you can use the following REST url:
POST /user/[userId]/groups
where the body of the post is:
{id: [groupId]}
assuming that id is the primary key of the Group model. Starting with v0.10-rc5, you can also simultaneously create and a add a new group to a user by sending data about the new group in the POST body, without an id:
{name: 'myGroup'}
You can currently only add one linked entity at a time.
To add an entity programmatically, use the add method:
User.findOne(123).exec(function(err, user) {
if (err) {return res.serverError(err);}
// Add group with ID 1 to user with ID 123
user.groups.add(1);
// Add brand new group to user with ID 123
user.groups.add({name: 'myGroup'});
// Save the user, committing the additions
user.save(function(err, user) {
if (err) {return res.serverError(err);}
return res.json(user);
});
});
Just to answer your question about accessing the join tables directly,
Yes you can do that if you are using Model.query function. You need to check the namees of the join tables from DB itself. Not sure if it is recommended or not but I have found myself in such situations sometimes when it was unavoidable.
There have been times when the logic I was trying to implement involved a lot many queries and it was required to be executed as an atomic transaction.
In those case, I encapsulated all the DB logic in a stored function and executed that using Model.query
var myQuery = "select some_db_function(" + <param> + ")";
Model.query(myQuery, function(err, result){
if(err) return res.json(err);
else{
result = result.rows[0].some_db_function;
return res.json(result);
}
});
postgres has been a great help here due to json datatype which allowed me to pass params as JSON and also return values as JSON