How to implement many to many association using through in Sails? - node.js

I'm using Sails v0.11.2 and MongoDB 3.2 on Mac OS X El Capitan and I'm trying to implement Many-To-Many association using Through option which isn't supported yet.
However, googling I found this Waterline Github Issue and elennaro, a github user, gave me a couple of links with some examples:
First one
Second one
I have tried to adapt them to my own Sails app but I can't make it works. I got no errors on the console but the record or document on the intermediary table is not created only the Form document in it's table.
These are my models:
User.js
module.exports = {
schema: true,
tableName: 'users',
autoCreatedAt: false,
autoUpdatedAt: false,
attributes:
{
email : { type: 'email', required: true, unique: true },
encrypted_password : { type: 'string' },
reset_password_token: { type: 'string', defaultsTo: null},
permission_level : { type: 'integer', required: true, min: 1, max: 3, defaultsTo: 0 },
belongs_to : { type: 'string', required: true, defaultsTo: 0 },
signin_count : { type: 'integer', required: true, defaultsTo: 1 },
status_active : { type: 'boolean', required: true, defaultsTo: false },
last_signin_at : { type: 'datetime', defaultsTo: function (){ return new Date(); } },
last_signin_ip : { type: 'string', defaultsTo: '0.0.0.0' },
// Add a reference to Person
person_id:
{
model: 'person'
},
// Add a reference to Forms collection
forms:
{
collection: 'form',
via: 'user_id',
through: 'userhasform'
},
has:
{
collection: 'userhasform',
via: 'form_id'
}
}
};
Form.js
module.exports = {
schema: true,
tableName: 'forms',
attributes:
{
name : { type: 'string', required: true, unique: true },
creator : { type: 'string', unique: false },
sequence: { type: 'integer', autoIncrement: true },
// Add a reference to Questions collection
questions:
{
collection: 'question',
via: 'form_id'
},
// Add a reference to the owners Users
owners: {
collection: 'user',
via: 'form_id',
through: 'userhasform'
}
}
};
UserHasForm.js
module.exports = {
schema: true,
tableName: 'users_have_forms',
attributes:
{
to_edit : { type: 'boolean' },
to_delete : { type: 'boolean' },
user_id : { model: 'user' },
form_id : { model: 'form' }
}
};
The controller in which I create a form and it is supposed the intermediary document is been created at the join table is:
FormController.js
module.exports = {
create: function (req, res)
{
var ownerJson = {},
tmpFolio;
// Get the logged user to make the Folio and then create the form
SessionService.getUser(req, createForm);
// Callback function
function createForm (err, session)
{
// If there's no logged user or any error
if (err || !session)
{
console.log(err);
return res.json(err.status, {error: err});
}
console.log('User to create Folio: ', session.id);
ownerJson.owner_a = session.first_name;
ownerJson.owner_b = session.second_name;
ownerJson.owner_c = session.last_name;
// Construct the Folio creator part like AVC
tmpFolio = FolioService.generateFolio(ownerJson);
Form.create({
name: req.body.name,
creator: tmpFolio
})
.then(function (form){
if (err)
{
console.log(err);
return res.json(err.status, {error: err});
}
// Create the jointable record
var createdRecord = UserHasForm.create({
to_edit: true,
to_delete: true,
user_id: session.id,
form_id: form.id
})
.then(function (createdRecord){
if (err)
{
console.log(err);
return res.json(err.status, {error: err});
}
return createdRecord;
});
return [form, createdRecord];
})
.spread(function (form, createdRecord){
return res.json(200,
{
message: 'The form was created successfuly!',
data: form,
sharing: createdRecord
});
})
.fail(function (err){
if (err)
{
console.log(err);
res.json(err.status, {error: err});
}
});
}
},
};
When I run this code I got the next error:
[ReferenceError: UserHasForm is not defined]
Unhandled rejection TypeError: Cannot read property 'toString' of undefined
So I suppose it can't find the model so I add the next line to the model at the beginning:
var UserHasForm = require('../models/UserHasForm');
And now I get the next error:
[TypeError: UserHasForm.create is not a function]
All this is following the the first example on the list.
Any idea why I'm getting this error?
Any kind of help will be welcomed!

Well after trying to many examples finally I found the solution thanks to #elennaro for all his support. The whole conversation could be found in the link to the chat we both started under the main question's comments.
Also I can tell you that the examples in the links provided by him (which are in the question above) works fine, the problem was that the version I was using didn't support the features that those examples show.
Basically what I had to do is to install the most recent version for NodeJS, SailsJS and Waterline.
In my case I actually have the next ones:
Node v5.3.0
Sails v0.11.3
Waterline v0.10.30
After that I have to make some changes to my models and at the end they look like this:
User.js
module.exports = {
schema: true,
tableName: 'users',
autoCreatedAt: false,
autoUpdatedAt: false,
attributes:
{
// username : { type: 'string', unique: true, minLength: 5, maxLength: 15 },
email : { type: 'email', required: true, unique: true },
encrypted_password : { type: 'string' },
reset_password_token: { type: 'string', defaultsTo: null},
permission_level : { type: 'integer', required: true, min: 1, max: 3, defaultsTo: 0 },
belongs_to : { type: 'string', required: true, defaultsTo: 0 },
signin_count : { type: 'integer', required: true, defaultsTo: 1 },
status_active : { type: 'boolean', required: true, defaultsTo: false },
last_signin_at : { type: 'datetime', defaultsTo: function (){ return new Date(); } },
last_signin_ip : { type: 'string', defaultsTo: '0.0.0.0' },
// Add a reference to Forms collection
forms:
{
collection: 'form',
via: 'user',
through: 'userhasform'
// dominant: true
}
}
};
Form.js
module.exports = {
schema: true,
tableName: 'forms',
attributes:
{
name : { type: 'string', required: true, unique: true },
creator : { type: 'string', unique: false },
sequence: { type: 'integer', autoIncrement: true },
// Add a reference to the owners Users
owners: {
collection: 'user',
via: 'form',
through: 'userhasform'
}
}
};
UserHasForm.js
module.exports = {
schema: true,
tableName: 'users_have_forms',
attributes:
{
to_edit : { type: 'boolean' },
to_delete : { type: 'boolean' },
user : { model: 'User', foreignKey: true, columnName: 'user_id' },
form : { model: 'Form', foreignKey: true, columnName: 'form_id' }
}
};
FormController.js
Still the same as in the question
I hope it could be useful for anybody. And once again thanks to # Alexander Arutinyants for your support!
Any question, please leave a comment!

Related

SQLite SequelizeJs adding an extra filed in query

I am using sequelize and SQLite3. When I use the model in my code then it is generating wrong query. Can any one help me to fix this issue
This is my model defiantion
module.exports = function(sequelize, DataTypes) {
let product = sequelize.define('product', {
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(200),
},
code: {
type: DataTypes.STRING(100),
},
desc: {
type: "BLOB",
},
productCategoryId: {
type: DataTypes.INTEGER(10).UNSIGNED,
references: {
model: 'product_category',
key: 'id'
}
},
costPrice: {
type: DataTypes.FLOAT,
},
sellPrice: {
type: DataTypes.FLOAT,
},
markup: {
type: DataTypes.FLOAT,
},
markupType: {
type: DataTypes.ENUM('AMOUNT','PERCENTAGE'),
},
imgAttachment: {
type: DataTypes.INTEGER(1),
},
minOrderQuantity: {
type: DataTypes.INTEGER(10),
},
minStockQuantity: {
type: DataTypes.INTEGER(10),
},
isComposite: {
type: DataTypes.INTEGER(1),
},
isAllowedOutOfStockSale: {
type: DataTypes.INTEGER(1),
defaultValue: '0'
},
isActive: {
type: DataTypes.INTEGER(1),
defaultValue: '0'
},
isDeceptive: {
type: DataTypes.INTEGER(1),
defaultValue: '0'
},
createdAt: {
type: DataTypes.DATE,
},
createdBy: {
type: DataTypes.INTEGER(10).UNSIGNED,
references: {
model: 'user',
key: 'id'
}
},
deletedAt: {
type: DataTypes.DATE,
},
deletedBy: {
type: DataTypes.INTEGER(10).UNSIGNED,
references: {
model: 'user',
key: 'id'
}
},
updatedAt: {
type: DataTypes.DATE,
},
updatedBy: {
type: DataTypes.INTEGER(10).UNSIGNED,
references: {
model: 'user',
key: 'id'
}
}
}, {
tableName: 'product',
timestamps: false,
defaultScope: {
where: {
isActive: true,
deletedAt: null,
}
}
});
// Association
product.associate = function(models) {
models.product.belongsTo(models.user);
models.product.belongsTo(models.user);
models.product.belongsTo(models.user);
models.product.belongsTo(models.product_category);
models.product.hasMany(models.product_composition);
};
return product;
}
This is my model implementation
models.findAll({})
.then(data => {
console.log(data)
.catch(err => {
console.log(err)
});
I am getting SequelizeDatabaseError. After investigation I trace out the generated query
SELECT `id`, `name`, `code`, `desc`, `productCategoryId`, `costPrice`, `sellPrice`, `markup`, `markupType`, `imgAttachment`, `minOrderQuantity`, `minStockQuantity`, `isComposite`, `isAllowedOutOfStockSale`, `isActive`, `isDeceptive`, `createdAt`, `createdBy`, `deletedAt`, `deletedBy`, `updatedAt`, `updatedBy`, `userId` FROM `product` AS `product` WHERE `product`.`id` = 1 AND `product`.`isActive` = 1 AND `product`.`deletedAt` IS NULL;
Why it is adding userId in query. This query works fine when I remove userId field from this generated query
That is because of this line :
This line will add a userId attribute to product to hold the primary key value for Product
models.product.belongsTo(models.user);
But This will not add the field , reason is , naming convention is followed for foreign key name productCategoryId but not in above case ,
models.product.belongsTo(models.product_category);
for that you should define that explicitly and you should also add alias name for association coz you are using one table for 3 relations , like
models.product.belongsTo(models.user , { as : 'delete_by' ,foreignKey: 'deletedBy'} );
models.product.belongsTo(models.user , { as : 'created_by' ,foreignKey: 'createdBy'} );
models.product.belongsTo(models.user , { as : 'updated_by' , foreignKey: 'updatedBy'} );
For more detail : DO READ

How to generate hash password and store it in database in sails.js

I want to create new user and save into user table in my data base
i act as follows:
create: function(req,res,next){
bcrypt.hash(req.param('password'),10,function (err, hashed) {
if (err) {
console.log("4");
return res.serverError({'err': 'hash Error!'});
} else{
User.create({
username: req.param('username'),
password: hashed,
type: req.param('type')
},function (err, created_user) {
if (err) {
err = validator(User, err);
return res.json({'status': false, 'errors': err.Errors});
}
return res.json({'status': true, 'result': created_user});
}
);
}
}
}
My User Model is:
module.exports = {
attributes: {
username: {
type: 'string',
required: true,
unique: true
},
password: {
type: 'string',
required: true
},
type: {
type: 'number',
columnType: 'integer'
},
last_x_map: {
type: 'number',
columnType: 'float'
},
last_y_map: {
type: 'number',
columnType: 'float'
},
places :{
collection: 'place',
via: 'user_owner'
},
},
validationMessages: {
username:{
required: '...',
alphanumericdashed: '...',
unique: '...'
},
password: {
required: '...'
}
},
But it always return false and do not any thing.
Where is the problem?
Or what do you recommend for this?
thanks a lot.

Sails js one to one populate conditions not working?

I'm newbie of Sails and I've got a problem with one to one association.
First, I have model User:
module.exports = {
schema: true,
identity : "User",
tableName: "user",
attributes: {
email: {
type: 'email',
unique: true,
required: true
},
password: {
type: 'string'
},
salt: {
type: 'string'
},
merchant: {
model: 'merchant',
defaultsTo: null
},
removed: {
type: 'boolean',
required: true,
defaultsTo: false
}
}
}
And my Merchant model:
module.exports = {
schema: true,
identity : "Merchant",
tableName: "merchant",
attributes: {
name: {
type: 'string',
unique: true,
required: true
},
code: {
type: 'string',
unique: true,
required: true
},
security_key: {
type: 'string',
required: true
},
active: {
type: 'boolean',
defaultsTo: false,
required: true
},
user: {
model: 'user'
}
}
}
So when I need to find records where merchant.active = true, I write this query:
var findUser = User.find(query).populate('merchant', {active: true});
return findUser;
But it was not working at all.
Anyone any ideas to solve this properly?
P.S. my Sails version is: 0.11.1. My DB is MongoDB
First of all, remove defaultsTo from your association attributes. I don't like this :) I don't know if it makes the problem, but in documentation I never see this.
Also you need to execute your query, not just return it. If I take your models' declarations then I can write populate query like this.
var findUser = User.find(query).populate('merchant', {active: true});
findUser.then(function(user) {
console.log('Your user is ' + user);
}).catch(function(error) {
console.log('Your error is ' + error);
});

Waterline trying to access ID field

Currently Im using this model (with sails.js)
module.exports = {
tableName: 'player_deaths',
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
player_id: {
required: true,
type: 'integer'
},
time: {
required: true,
type: 'integer'
},
level: {
required: true,
type: 'integer'
},
killed_by: {
required: true,
type: 'string'
},
is_player: {
required: true,
type: 'integer'
},
mostdamage_by: {
required: true,
type: 'string'
},
mostdamage_is_player: {
required: true,
type: 'integer'
},
unjustified: {
required: true,
type: 'integer'
},
mostdamage_unjustified: {
required: true,
type: 'integer'
}
},
autoPk: false,
}
And im calling it like this
PlayersDeaths.find().sort('time desc').exec(function(err, data) {
if(err || data.length === 0) {
console.log(err,data)
req.flash('errors', 'No deaths found');
return res.redirect('/');
}
return res.view('community/deaths', { deaths: data});
});
Thing is im getting this error
Unknown column 'playersdeath.id' in field list
Checking my model I dont even see the ID attribute listed... so why is it trying to access it?!
(the table does not have a pk)
Looks like a primary key is required. If you use autoPk:false then you need to define a PK your self see https://github.com/balderdashy/waterline-docs/blob/master/models.md#autopk
However, you still maybe able to avoid this by using model.native() or model.query() (depending on your adapter)
http://sailsjs.org/#!/documentation/reference/waterline/models/native.html
http://sailsjs.org/#!/documentation/reference/waterline/models/query.html
If you have a database Schema defined, maybe you need set you model like this
module.exports = {
migrate: 'safe',
autoPK : false,
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
// ------
}

How use a findOne query method in a custom instance method

I'm trying to do the following:
// Task Model
module.exports = {
schema: true,
attributes: {
directProy: {
type: 'string',
required: true
},
user: {
type: 'string',
required: true
},
checkUser: {
type: 'boolean',
defaultsTo: false
},
proy: {
type: 'string',
required: true
},
pieza: {
type: 'string',
required: true
},
hours: {
type: 'string',
required: true
},
obs: {
type: 'text',
defaultsTo: "lorem ipsum"
},
check: {
type: 'boolean',
defaultsTo: false
},
userName: function() {
User.findOne(this.user).done(function(err, user){
if(err){ return err;}
return user.name;
});
}
}
};
In the method "userName" I'm trying to get the name of a user with the ID it stored in the "user" attribute.
but when I run the "username" method, brings me back "undefined", I think this has to be a problem of asynchronous type
Would greatly appreciate the help they can give me since I have no idea how to associate values ​​between models and this is very useful
try passing a callback.
userName: function(cb) {
User.findOne(this.user).done(function(err, user){
cb(err, user.name);
});
}
Then when you are calling it, make sure to pass a callback.
model.userName(function(err, username) {
console.log(username);
});
your should use .exec instead of .done since it will not be avalible in sails#0.10
http://beta.sailsjs.org/#/documentation/reference/Models

Resources