extra columns in model association sailsjs - sails.js

How can I have an extra column in postgres with sailsjs model association?
This is an example of my two models
// Users.js attribute
...
challenges: {
collection: 'challenge',
via: 'userChallenge'
}
// Challenge.js attribute
...
userChallenge: {
collection: 'users',
via: 'challenges'
}
...
With this I get the table association (many to many)
id | challenge_userChallenge | users_challenges
I need one or more extra columns like "active" or something like that
Thanks in advance

You should use through associations.
Many-to-Many through associations behave the same way as many-to-many
associations with the exception of the join table being automatically
created for you. In a Many-To-Many through assocation you define a
model containing two fields that correspond to the two models you will
be joining together. When defining an association you will add the
through key to show that the model should be used rather than the
automatic join table.
Let's take the Post and Tag models as an example. The Post has and belongs to many Tag and the Tag has and belongs to many Post. These two models will be joined via the PostTag model.
Our Post model:
/**
* Post.js
*
* #description :: A model definition. Represents a database table/collection/etc.
* #docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
*/
module.exports = {
tableName: 'posts',
attributes: {
name: {
type: 'string',
required: true
},
// Many to many: Post has and belongs to many Tag.
tags: {
collection: 'Tag',
via: 'postId',
through: 'PostTag'
}
};
Our Tag model:
/**
* Tag.js
*
* #description :: A model definition. Represents a database table/collection/etc.
* #docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
*/
module.exports = {
tableName: 'tags',
attributes: {
name: {
type: 'string',
unique: true,
required: true
},
// Many to many: Tag has and belongs to many Post.
posts: {
collection: 'Post',
via: 'tagId',
through: 'PostTag'
}
}
};
Our PostTag model (we're creating it manually, we don't want Sails.js to create it automatically):
/**
* PostTag.js
*
* #description :: A model definition. Represents a database table/collection/etc.
* #docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
*/
module.exports = {
tableName: 'posts_tags',
attributes: {
postId: {
model: 'Post'
},
tagId: {
model: 'Tag'
}
}
};
The PostTag model is actually the join table. In this model, you can define your extra fields.
Hope this helps.

While the answer by Vladyslav Turak is correct for Sails v1.0 and up, please note that Through Associations are NOT SUPPORTED in Sails 0.12.
To achieve the similar effect with Sails 0.12, you can use the following:
The Post model:
/**
* Post.js
*
* #description :: A model definition. Represents a database table/collection/etc.
* #docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
*/
module.exports = {
attributes: {
name: {
type: 'string',
required: true
},
// Many to many: Post has and belongs to many PostTag.
tags: {
collection: 'PostTag',
via: 'post'
}
};
The Tag model:
/**
* Tag.js
*
* #description :: A model definition. Represents a database table/collection/etc.
* #docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
*/
module.exports = {
attributes: {
name: {
type: 'string',
unique: true,
required: true
},
// Many to many: Tag has and belongs to many PostTag.
posts: {
collection: 'PostTag',
via: 'tag',
}
}
};
Our PostTag model (we're creating it manually, we don't want Sails.js to create it automatically):
/**
* PostTag.js
*
* #description :: A model definition. Represents a database table/collection/etc.
* #docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
*/
module.exports = {
attributes: {
post: {
model: 'Post'
},
tag: {
model: 'Tag'
},
customField: {
type: 'string'
}
}
};
The PostTag model is actually the join table. In this model, you can define your extra fields.
Hope this helps someone using Sails v0.12.

Related

Create unique multikey index via model settings

I am using Sails v1.1 -
I created a many-to-many through custom model association following the sails doc here - https://sailsjs.com/documentation/concepts/models-and-orm/associations/through-associations
The PetUser model has two columns pet and user, where each is the respective id. I want to create a unique multi-key index, meaning there cannot be two rows with the same combination of "pet and user". Meaning the second call should succeed, and third call should fail with uniqueness error:
await PetUser.create({ user: 1, pet: 33 }); // should succeed
await PetUser.create({ user: 1, pet: 44 }); // should succeed as user/pet combination is different
await PetUser.create({ user: 1, pet: 33 }); // should fail
I tried adding unique: true to both the owner and pet attribute on PetUser model below, but only the first unique: true gets respected.
So this is my code in myApp/api/models/PetUser.js
module.exports = {
attributes: {
owner: {
model:'user',
unique: true
},
pet: {
model: 'pet',
unique: true
}
}
}
For implementing similar behavior I added a combined attribute and mark it unique. Also, I added beforeCreate and beforeUpdate model hooks on which I generate my combined attribute to check is it unique or not.
const YourModel = {
attributes: {
owner: {
model: 'user',
},
pet: {
model: 'pet',
},
petOwner: {
type: 'string',
unique: true,
}
},
beforeCreate : function(values,cb) {
// TODO get ids from related records or reset to default on missed relation record if you need it
const petId = 35;
const ownerId = 8;
values.petOwner = `${petId}-${ownerId}`;
cb();
},
beforeUpdate : function(values,cb) {
YourModel.beforeCreate(values, cb)
},
};
module.exports = YourModel;
In result when you tries to add the record with the same relations, you will get E_UNIQUE as you expected.

How to add record to sailsjs through association using the Blueprint REST API

I need to implement a through association in order to have a many-to-many relationship with a custom field in the join table. In SailsJS through associations require three models; two models are your business objects, Species and Lands in my case, and a third representing the join table SpeciesLands. See my SailsJS models at the bottom of this post.
Once I have the association set up how do I go about associating two objects through the blueprint API? Do I need to POST data to the /specieslands endpoint using the id of the Species and Land I want to link? Is it possible to create and link objects at the same time like you can with the many-to-many relationship? Is this something that needs to be done in a SailsJS controller rather than through the blueprint API?
Here are my models:
Species
module.exports = {
attributes: {
scientificName: {
type: 'string',
required: true,
unique: true
},
commonName: {
type: 'string'
},
taxon: {
type: 'string',
required: true
},
leadOffice: {
type: 'string'
},
lands: {
collection: 'lands',
via: 'land',
through: 'specieslands'
}
}
};
Lands
module.exports = {
attributes: {
agency: {
type: 'string',
required: true
},
name: {
type: 'string',
required: true,
unique: true
},
species: {
collection: 'species',
via: 'species',
through: 'specieslands'
}
}
};
SpeciesLands
module.exports = {
attributes: {
species: {
model: 'species'
},
land: {
model: 'lands'
},
population: {
type: 'string',
required: true,
enum: ['O', 'O+', 'P', 'U'] // THIS IS THE REASON FOR ASSOCIATION
}
}
};
I do not think it is possible like that.
I am pretty sure you have to:
Create Specie
Lands
Relation
1 & 2 using POST /modelName
3 using http://sailsjs.com/documentation/reference/blueprint-api/add-to
When using a through association, the Blueprint API will expect associations to be generated by POST into the associative model (The relation model).
In your case, if you wanted to associate an item from the Species model with an item from the Lands model, you would do something like this:
curl --header "Content-Type: application/json"
--request POST
--data '{species:15,land:51,population:"O"}'
http://www.example.com/SpeciesLands
Or in Postman:

How can I instantiate a model in Lifecycle callbacks in sails?

How can I instantiate a model in Lifecycle callbacks different than this? After I delete a record on the parent model I want to delete those associated records of the son model in afterDestroy.
For example:
/**
* Survey.js
*
*/
attributes: {
question: {
type: 'string',
required: true
},
active: {
type: 'boolean'
},
// Below is all specification for relations to another models
answers: {
collection: 'answer',
via: 'answer'
}
},
// Lifecycle Callbacks
afterDestroy: function (destroyedRecords, cb) {
answer.destroy({survey: destroyedRecords[0].id}).exec(function(err, answers) {
console.log(answers);
});
cb();
}
});
With this I received an error that 'answer' is not defined
Solved.
To instantiate a model in Lifecycle callbacks in Sails different than 'this' you need you need to precede the model with sails.models.
For the previous code:
// Lifecycle Callbacks
afterDestroy: function (destroyedRecords, cb) {
sails.models.answer.destroy({survey: destroyedRecords[0].id}).exec(function(err, answers) {
console.log(answers);
});
cb();

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.