Sails/Waterline Populate doesn't work as expected - mysql

I'm bangin' my head against the wall in the last hours, and I can't figure out a solution to my problem. In my sails models, I have 2 one-to-many associations. 'A' model can have many 'B', and 'B' model can have many 'C'. In my controller, when I do a.find().populate('b') (...) it returns me the entire collection of A model, but populate each entry of A model with only one B, which doesn't match the current dataset I have in my database(mysql). And doesn't populate the C entries in the B model. In other words, I'm trying to achieve something like nested population.
It's something wrong with the Controller code, right? How can I make this work?
Thanks in advance!
EDIT:
Company.js
module.exports = {
identity: 'company',
attributes: {
name: {
type: 'string',
required: true
},
address: {
type: 'string',
required: true
},
zip_code: {
type: 'string',
required: true
},
city: {
type: 'string',
required: true
},
nif: {
type: 'integer',
required: true,
minLength: 9
},
country: {
type: 'string',
required: true
},
phone_number: {
type: 'string',
required: true
},
email: {
type: 'email',
required: true
},
facilities: {
collection: 'facility',
references: 'facility',
on: 'id',
via: 'company'
}
}
};
Facility.js
module.exports = {
identity: 'facility',
attributes: {
company: {
columnName: 'id_company',
model: 'company'
},
warehouses: {
collection: 'warehouse',
references: 'warehouse',
on: 'id',
via: 'facility'
},
name: {
type: 'string',
required: true
},
address: {
type: 'string',
required: true
},
zip_code: {
type: 'string',
required: true
},
city: {
type: 'string',
required: true
},
country: {
type: 'string',
required: true
},
phone_number: {
type: 'string',
},
email: {
type: 'email',
},
longitude: {
type: 'float',
},
latitude: {
type: 'float'
}
}
};
Warehouse.js
module.exports = {
identity: 'warehouse',
attributes: {
facility: {
columnName: 'id_facility',
model: 'facility',
},
name: {
type: 'string',
required: true
},
longitude: {
type: 'float',
},
latitude: {
type: 'float'
}
}
};
MainController's relevant code:
companies: function(req, res) {
company.find().populate('facilities').exec(function(err, comp){
var error = '';
if(err){
error = 'Unable to retrieve the requested information. Try again later and, if the problem persists, contact the platform administrator.';
} else if(!comp[0]) {
error = 'There\'s no company data inserted.';
}
// (...)
});
},

You should remove the references and on from your models.
Regarding nested population, just like I said in the comment, Waterline does not currently support it. You could check Waterline2 which, as they say, offers the possibility of nested populating, but is not recommended for production.
Otherwise you could check this out: Sails.js populate nested associations

Related

Sequalize association fails (BelongsTo) NodeJs Sequalize

Trying to work with sequalize association ( belongsTo and hasMany() ) but when i tries to query findAll with include the Model which was associated it fails by saying Models are not associated.
User Model
const {DATE, STRING, INTEGER} = require("sequelize");
const {USER} = require("../config/db.config");
module.exports = (sequelize, Sequelize) => {
const User = sequelize.define("Users", {
id: {
primaryKey: true, type: INTEGER, required: true, autoIncrement: true
}, phoneNumber: {
type: STRING, required: true, unique: true
}, firstname: {
type: STRING, required: true
}, middlename: {
type: STRING, required: false
}, region: {
type: STRING, required: true
}, district: {
type: STRING, required: true
}, street: {
type: STRING, required: true
}, lastname: {
type: STRING, required: true,
}, 'type': {
type: STRING, required: true,
}, email: {
type: STRING, required: true, unique: true
}, status: {
type: Sequelize.ENUM("ACTIVE", "NOT-ACTIVE", "SUSPENDED"), defaultValue: "NOT-ACTIVE",
}, createdAt: {
type: DATE,
}, updatedAt: {
type: DATE,
}
});
User.associate = function (models) {
User.hasMany(models.Students, {foreignKey: 'userId', as: 'user'});
}
return User;
};
Student Model
const {DATE, STRING, INTEGER} = require("sequelize");
module.exports = (sequelize, Sequelize) => {
const Student = sequelize.define("Students",
{
id: {
primaryKey: true, type: STRING, required: true
}, userId: {
type: INTEGER, required: true,
}, status: {
type: Sequelize.ENUM("ACTIVE", "NOT-ACTIVE", "SUSPENDED"), defaultValue: "NOT-ACTIVE",
},
level: {
type: STRING, required: true,
}, createdAt: {
type: DATE,
}, updatedAt: {
type: DATE,
}
});
Student.associate = function (models) {
Student.belongsTo(models.Users, {foreignKey: 'userId', as: 'user'})
Student.belongsToMany(models.Classes, {through: 'StudentClass',})
}
return Student;
};
I have tried to query them by using the bellow function but i got nothing
function getAllStudents() {
Student.findAll({include: [{model: User, as: 'user', required: true}]}).then(value => {
console.log(value)
return value;
}).catch(reason => {
console.error(reason)
})
}
But i get the following
Result i get

How to create a joint column in mysql off of a calculation of other columns

I'm trying to creat a 'total column' in my user model. I'm not sure how I can do the calculations. Previously I was doing it on the front end, but that leaves me with very limited sorting options, so I would like to do this on the back end, then append the total row instead.
This is what my model looks like:
module.exports = {
autosubscribe: ['destroy'],
attributes: {
name: {
type: 'string',
required: true
},
email: {
type: 'email',
unique: true,
required: true
},
password: {
type: 'string',
required: true,
minLength: 6
},
status: {
type: 'string',
defaultsTo: 'offline',
required: false
},
score: {
type: 'integer',
defaultsTo: 0,
required: false
},
totalwins: {
type: 'integer',
defaultsTo: 0,
required: false
},
totalgames: {
type: 'integer',
defaultsTo: 0,
required: false
},
ip: {
type: 'string',
required: false
}
}
};
I tried adding something like
scorepct: {
type: 'integer',
defaultsTo: totalwins/totalgames,
required: false
}
But this doesn't seem to work, are there any ideas on how I can do this from my User model?
I think the only way to do what you want is to add lifecycle callbacks to your model. You can make sure that every time you use the default create or update methods, that property is updated as you wish:
attributes: {
// ...
scorepct: {
type: 'integer'
defaultsTo: 0 // always 0 at creation time
},
},
afterUpdate: function(attrs, next) {
var calculatedPct = attrs.totalgames === 0 ? 0 : attrs.totalwins / attrs.totalgames;
if (calculatedPct === attrs.scorepct) {
return next();
}
User.update(attrs.id, {scorepct: calculatedPct}).exec(function(err, user) {
// handle the error
return next();
});
},
This seems like a lot of trouble - why not just calculate the percentage wherever you need it without storing the result in the db? You could arrange to do this automatically at every .find, or do this when making api calls for data, etc.
Looking at the docs, maybe this will work
scorepct: {
type: 'integer'
defaultsTo: function(){ return this.totalwins / this.totalgames }
required: false
}
or
scorepct: function(){ return this.totalwins / this.totalgames }

Unable to run a join query using sequelize

I need to run a join query using sequelize and I have been reading the documentation at sequelize doc. But as I run the following snippet, I get an error.
let channelUsersM = UserModel.get(); // Table name: channel_users
let channelM = ChannelModel.get(); // Table name: channel
channelUsersM.belongsTo(channelM, {as: 'channel',foreign_key: 'channel_id',targetKey:'id'});
channelM.hasMany(channelUsersM,{foreign_key: 'channel_id'});
channelUsersM.findAll({
attributes: ['username'],
where: {
usertype: this.userType,
channel: {
name: channelName
}
},
include: [channelM]
}).then((r) => {
resolve(r);
}).catch((err) => {
reject(err);
});
Error says: channel is not associated to channel_users!
What could be the reason for this? I know how to directly run a SQL query using sequelize, but I do not want to go with it.
For easier understanding here, is the equivalent sql query that I am trying with sequelize:
select cu.username from channel as ch left join
channel_users as cu on ch.id = cu.channel_id
ch.name = 'some-name' and cu.usertype = 'some-type';
Here is the definition of models if required:
For channel_users:
channel_id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
field: 'channel_id'
},
userid: {
type: Sequelize.INTEGER,
field: 'userid'
},
username: {
type: Sequelize.CHAR(255),
field: 'username'
},
password: {
type: Sequelize.TEXT,
field: 'password'
},
usertype: {
type: Sequelize.ENUM('user', 'moderator','speaker','owner'),
field: 'usertype'
}
For channel:
id: {
type: Sequelize.INTEGER,
field: 'id',
autoIncrement: true,
primaryKey: true
},
name: {
type: Sequelize.CHAR(255),
field: 'name'
},
display_name: {
type: Sequelize.TEXT,
field: 'display_name'
},
creatorid: {
type: Sequelize.INTEGER,
field: 'creatorid'
},
password: {
type: Sequelize.TEXT,
field: 'password'
},
createdAt: {
type: Sequelize.DATE,
field: 'createdAt'
},
modifiedAt: {
type: Sequelize.DATE,
field: 'modifiedAt'
}
You have defined an alias in the belongsTo association, so you also need to include the alias in include attribute when querying. Moreover, the channel.name column value should also be included in the include object of the query.
channelUsersM.findAll({
attributes: ['username'],
where: {
usertype: this.userType
},
include: [
{
model: channelM,
as: 'channel',
attributes: [],
where: { name: channelName }
}
]
}).then((r) => {
resolve(r);
}).catch((err) => {
reject(err);
});
The attributes: [] in include is added in order to prevent returning any fields from the channel table (according to you query you want only the username field from channel_users table).

Meteor: JSON doesn't match SimpleSchema

I got this SimpleSchema for a collection in my meteor-app
Collection.attachSchema(new SimpleSchema({
title: { type: String },
slug: { type: String, unique: true },
language: { type: String, defaultValue: "en" },
'category.element': { type: String, optional: true }
}));
And I try to insert this JSON-data, but I get insert failed: Error: Category must be an object at getErrorObject
{
"_id" : "25uAB4TfeSfwAFRgv",
"title" : "Test 123",
"slug" : "test_123",
"language" : "en",
"category" : [
{
"element" : "Anything"
}
]
}
What is wrong with my JSON-data? Or what's wrong with my SimpleSchema. I can change both of them to match the best way.
You need to first declare the object, like,
Collection.attachSchema(new SimpleSchema({
...,
....,
category: {type: [Object], optional: true}
}));
After that you can extend/define object field(s) like,
Collection.attachSchema(new SimpleSchema({
....,
....,
category: {type: [Object]},
'category.$.element': {type: String}
}));
use '$' if its a Array Object ([Object]), If only object then then do not use '$'.
If you do not sure about Object Structure, use another parameter blackbox:true
like,
category: {type: [Object], blackbox: true}
The simplest solution is to define category as an array of objects in your schema:
Collection.attachSchema(new SimpleSchema({
title: { type: String },
slug: { type: String, unique: true },
language: { type: String, defaultValue: "en" },
category: { type: [Object], optional: true }
}));
This will get you unstuck.
If you want to be more specific about the contents of category then you can define a sub-schema for category. For example:
CategorySchema = new SimpleSchema({
element: { type: String }
});
Collection.attachSchema(new SimpleSchema({
title: { type: String },
slug: { type: String, unique: true },
language: { type: String, defaultValue: "en" },
category: { type: [CategorySchema], optional: true }
}));

Binding json data into store ubsing extjs

i have json data like this
{"GetStudentDetails":
{"TotalCount":5,
"RootResults":[
{"city":"West Chester","country":"USA","state":"PA ","student_id":100},
{"city":"Philly","country":"USA","state":"PA","student_id":101},
{"city":"Buffalo","country":"USA","state":"NY","student_id":102},
{"city":"Naigra City","country":"USA","state":"NY","student_id":103},
{"city":"West Chester","country":"USA","state":"PA","student_id":104}]
}
}
How to get this data into a store?
i am trying using a model like this.
Ext.define('User', {
extend: 'Ext.data.Model',
fields: [
{ type: 'string', name: 'TotalCount' }
],
hasMany: [{ model: 'RootResults', name: 'RootResult'}]
});
Ext.define("RootResults", {
extend: 'Ext.data.Model',
fields: [
{ type: 'string', name: 'city' },
{ type: 'string', name: 'country' },
{ type: 'string', name: 'state' },
{ type: 'string', name: 'student_id' }
],
belongsTo: 'User'
});
var store = Ext.create('Ext.data.Store', {
model: 'User',
proxy: {
type: 'ajax',
url: 'users.json',
reader: {
type: 'json'
}
}
});
How should my model be? when i am giving some more simple json i am getting the store loaded. i think the problem is with mapping?
Define model as
Ext.define("RootResults", {
extend: 'Ext.data.Model',
fields: [
{ type: 'string', name: 'city' },
{ type: 'string', name: 'country' },
{ type: 'string', name: 'state' },
{ type: 'string', name: 'student_id' }
],
});
And inside the reader definition add two parameters:
root: 'GetStudentDetails.RootResults'
totalProperty: 'GetStudentDetails.TotalCount'
Something like that... Main idea - don't try to bring internal JSON structure to your model - it should be reader responsibility to properly parse it.