Sailsjs Model.find() not retrieving foreign key - sails.js

I have two models with a one-to-many association in sails v0.12: a Game model and a User model (one game has two users).
When I find a user, I would like to see its foreign key to the game object. I recall that I used to see this attribute in a previous version of sails, but it seems to have disappeared.
Here is a simplified version of my Models:
Game.js:
module.exports = {
name: {
type: 'string',
},
players: {
collection: 'user',
defaultsTo: []
}
};
and User.js:
module.exports = {
email: {
type: 'string',
required: true
},
game: {
model: 'game'
}
};
I would like to find a user model and see BOTH its 'email' and 'game' attributes. Here is a sample query:
User.find(7).exec(function gotUser(user) {
console.log(user); // -> {'email': 'someUserEmail'} MISSING 'game'
}
EDIT:
The 'game' attribute does not appear even when calling populateAll() on the find():
User.find(7).populateAll().exec(function gotPopulatedUser(user) {
console.log(user); // -> {'email': 'someUserEmail'} MISSING 'game'
}
Is there a way to see the ID of the game that the user is associated (the foreign key) with upon finding the user? Or do I need to search for all games that have user's ID in their players collection? What would be the syntax for that?

This was my mistake!
The model definition for the Game object was missing a 'via' key.
Somehow, the two had been associated adequately for populating game objects without this (game.find().populateAll() would successfully populate the 'players' attribute), but the Game model should read:
module.exports = {
name: {
type: 'string'
},
players: {
collection: 'user',
via: 'game', //This was missing
defaultsTo: []
}
};
With this line in place, User.find() will yield a user that has the ID of the associated game as its 'game' attribute, and user.find.populateAll() will yield a user that has the populated game as its 'game' object, as expected.

Related

Call native blueprint actions from custom route

On different projects, I've been stucking on a very basic idea.
Everytime, that's the same concern. I want to add a new record to an association, but grabbing the parent without it's primary key.
For example, let's take a api/models/car.js model :
module.exports = {
attributes: {
licensePlate: {
type: 'integer',
required: true,
unique: true
},
locations: {
collection: 'location',
via: 'car'
}
}
};
And an api/models/location.js model :
module.exports = {
attributes: {
coordinates: {
type: 'array',
required: true
},
car: {
model: 'car'
}
}
};
A car can have multiple locations, a location have a single car.
I'm able to add a location to car using the native addTo blueprint action
POST /car/1/locations
{"coordinates":[2.13654,50.323654]}
Now what if, for some reason, I've no access to the car identifier, and feel like using another field, like a unique licensePlate ?
Basically, I would make a custom route inside config/routes, like
POST /car/byplate/:licensePlate/locations': {
controller: 'Car',
action: 'addLocationByPlate'
}
In order to be able to call
POST /car/byplate/AW45RE65/locations
{"coordinates":[2.13654,50.323654]}
And here is the problem... opening my fresh new action controller, I realize that, despite selecting my car by plate, the following logic (validation, location creation, location creation publish, location add to car's locations collection, location addition publish, error handling) is already implemented in sails.js core.
So here is the question:
How to properly call a native blueprint action with a custom route ?
In your controller you can write something like this:
YourModelName.Query(data, function(err, items){
if(err) return err;
res.json(items);
})
So for example if you want to create a new object in Car model you can do something like this:
Car.create({"carID": req.param("carID")}, function(err, items){
if(err) return err;
res.json(items);
})
this will createa new object with the ID you sent as a param.
Same goes for the other queries like add to, update, destroy etc.

sailsjs / waterline query "where" not empty

Hy there,
Before going to the hacky / cutom way i wanted to know if there is a built in query way to check for an empty / non empty many to many relationship as i was not successfull neither on google nor the doc.
If i take the example in the doc let's imagine i want to retrive a user only if he has a a Pet or Retrive a Pet without any Owner through a query.
// A user may have many pets
var User = Waterline.Collection.extend({
identity: 'user',
connection: 'local-postgresql',
attributes: {
firstName: 'string',
lastName: 'string',
// Add a reference to Pet
pets: {
collection: 'pet',
via: 'owners',
dominant: true
}
}
});
// A pet may have many owners
var Pet = Waterline.Collection.extend({
identity: 'pet',
connection: 'local-postgresql',
attributes: {
breed: 'string',
type: 'string',
name: 'string',
// Add a reference to User
owners: {
collection: 'user',
via: 'pets'
}
}
});
P.s. i know how to filter results after query execution that's not what i'm asking :)
There is nothing built in (aka User.hasPet() ) or something like that., so the simple answer is NO
If I know of these issues before hand I tend to write my DB in such a way that the queries are fast. IE: the User schema would have a hasPets column. Whenever a pet is added/removed a callbacks hits the user table to mark that field if it has an owner or not. So then I can query User.findAll({hasPet:true}).
Its a little much, but it depends on where you speed is needed.
This is a bit late, but I wanted to let you know it's pretty easy to do this with the Waterline ORM lifecycle functions. I've done it in a few of my projects. Basically, use the beforeCreate and beforeUpdate functions to set your flags. For your user, it might look like...
var User = Waterline.Collection.extend({
identity: 'user',
connection: 'local-postgresql',
beforeCreate: function(values, next) {
if (values.pets) {
values.has_pet = true;
} else {
values.has_pet = false;
}
next();
}
beforeUpdate: function(values, next) {
if (values.pets) {
values.has_pet = true;
} else {
values.has_pet = false;
}
next();
}
attributes: {
firstName: 'string',
lastName: 'string',
// Add a reference to Pet
pets: {
collection: 'pet',
via: 'owners',
dominant: true
},
has_pet: {
type: 'boolean'
}
}
});
Then you can query based on the has_pet attribute

Sailsjs add one to many association to a model during beforeCreate

I am trying to give a default association from a user to a pet, whenever a new User created.
Model:: User.js
var User = {
attributes: {
name: {type: 'string'},
// Add a One Way Relation to pet model
pets: {
collection: 'pet'
},
},
/*** This did not work ***/
beforeCreate: function (user, next) {
var defaultPet = {name: 'Default Pet 1'};
Pet.find(defaultPet).exec(function(err, pet) {
user.name = "BEFORECREATE",
user.pets = pet[0].id;
next();
});
},
}
module.exports = User;
However when a new record is created the user.pet is [ ], but user.name is changed to "BEFORECREATE".
How do I get user.pets = [{name: 'Default Pet 1'}] automatically for the new user created?
Or is there a better place for setting such defaults?
----- UPDATE: More info
I am using sails-disk currently.
Model: Pet.js
module.exports = {
attributes: {
name: {
type: 'string',
required: true
}
}
};
You can't add associations to a model in a lifecycle callback like beforeCreate. Currently, Waterline looks for and processes "nested models" like these before lifecycle callbacks run, so by the time beforeCreate is called it's too late. The simplest solution would be to create a User.createUser class method that wraps the logic you want:
createUser: function(data, cb) {
// If there are already pets specified, just use those
if (data.pets) {return User.create(data).exec(cb);}
// Otherwise look up the default pet
Pet.findOne({name:"Default Pet 1"}).exec(function(err,pet) {
// Return in case of error
if (err) {return cb(err);}
// Assuming the default pet exists, attach it
if (pet) {
console.log("SETTING DEFAULT PET", pet.id);
data.pets = [pet.id];
}
// Create the pet
return User.create(data).exec(cb);
});
}
A few notes:
In your example you were setting pets directly to an ID, but since it's a collection you must set it to an array.
If you're using the sails-disk adapter, you'll need to set schema: true in your model for this to work.
The new User model you get back will not be populated; you'll have to do a find with a populate('pets') with the new User ID to get the pet data attached.

Remove undesirable fields from being returned in sails

I am new to SailsJS and stuck in Data Model as follows:
I have a User model as follows:
module.exports = {
attributes: {
firstName: {
type: 'string'
},
email: {
type: 'email',
required: true
},
password: {
type: 'String'
},
passwordSalt: {
type: 'String'
},
projects:{
collection: 'ProjectMember',
via: 'userId'
}
}
};
Task Model :
module.exports = {
taskName: {
type: 'String'
},
userId: {
model: 'User'
}
};
In Task model, it is getting all fields from User table which is not required while task data is rendered. I was planning to create one more model called TinyUser which stores only required fields to be shown when task data is rendered.
But TinyUser should just refer User table and get required data from it rather than we creating all data for TinyUser manually when user data is created.
Is there any way this can be achieved in Sails?
Thanks in Advance..
I'm not sure about your question, but this will return a list of required attributes for any model
_.find(sails.models.<YOUR MODEL>._attributes, function(attr){return attr.required})
If your intent it to simply remove undesirable fields you can override the toJSON / toObject methods
see
https://github.com/balderdashy/waterline-docs/blob/master/models.md#toobjecttojson-instance-methods
User.find({select:['firstName', 'email']}).exec()

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.