I am using sails v0.12 , I have different models in my MySql relational database but the area of concern lies in these 2 models which are
1. User
2. Appointment
I want to add apptCustomer and apptProvider data into appointment model
but the value is NULL in the database
My User model is :
module.exports = {
attributes: {
name: {
type: "string",
},
email: {
type: "email",
required: true,
unique: true
},
contact_no:{
type: "int",
maxLength: 15,
//required: true,
},
address: {
type:"longtext",
//required: true,
},
userId:{
type: "string",
primaryKey: true,
unique:true
},
gtoken:{
type: "string"
},
provider:{
type:"string"
},
cancel:{
type: "boolean",
// required: true
},
business_name:{
type:"string",
unique:true
},
business_category:{
type:"string"
},
roles:{ // Many to Many = User <-> User-role <-> Role
collection:'Role',
through:'userrole',
},
services:{
collection:'Service',
through:'serviceprovider', // Many to Many = User (provider) <-> Service-provider <-> Service
},
schedules:{ // One to One = User (provider) - Schedule
collection:'Schedule',
via:'owner',
},
providerAppointments:{ // One to Many = User(customer) - multiple Appointments
collection:'Appointment',
via:'appProvider',
},
customerAppointments:{
collection:'Appointment', // One to Many = User(provider) - multiple Appointments
via:'appCustomer'
}
}
};
And my Appointment Model is
module.exports = {
attributes: {
appointment_id:{
type:'int',
primaryKey: true,
autoIncrement:true
},
appointmentDate: {
type: 'datetime',
required: true,
},
start_time: {
type: 'string',
required: true,
},
end_time: {
type: 'string',
},
status: {
type: 'string',
enum: ['booked', 'canceled']
},
appProvider: {
model: 'user',
},
appCustomer:{
model: 'user',
},
serviceAppointment: {
model: 'service',
}
}
};
And my Model Methods are as follows
Appointment.findOrCreate({appointmentDate:req.body.values.appointmentDate, start_time:req.body.values.start_time, end_time:req.body.end_time, status: status},{appointmentDate:req.body.values.appointmentDate, start_time:req.body.values.start_time, end_time:req.body.end_time, status: status})
.exec(function apptCreated(err,appt){
if(err) { sails.log('err',err)}
Service.findOne({ service_id : req.body.values.selected_service})
.exec(function(err,service){
service.serviceAppointments.add(appt);
service.save(function(err,result){
if(err) { sails.log(err)}
})
}),
User.find({userId: req.body.businessId})
.populate('roles')
.exec(function(err,provider){
_.map( provider.roles, role => { // Problem lies here .. this method is not working
if(role.role_id==1){
provider.providerAppointments.add(appt);
provider.save(function(err, result){
if(err) { sails.log(err)}
sails.log('appointment added to provider')
})
}
})
}),
//Appointment adding to Customer
User.find({userId: req.body.customerId})
.populate('roles')
.exec(function(err,customer){
_.map( customer.roles, role => { // Problem lies here... this method is not working
if(role.role_id==2){
customer.customerAppointments.add(appt)
customer.save(function(err, result){
if(err) { sails.log(err)}
sails.log('appointment added to customer')
})
}
})
}),
// Adding contact to customer
User.update({userId: req.body.customerId},{ contact_no: req.body.values.contact_no}) // this method works fine
.exec(function userUpdated(err, user){
if(err) { return sails.log(err)}
sails.log('contact number updated',user);
})
})
As far as I can see, at the moment that you call provider.providerAppointments.add, provider.providerAppointments is still just a regular property - possibly an array of ids.
I think you need to add .populate('providerAppointments') to your User.find... if you do that, then provider.providerAppointments should have a .add method that works the way you expect.
Of course, if this is the source of error, I would have expected a pretty clear error message, like provider.providerAppointments.add is not a function or some such. But try adding the populate, see if it fixes your problem.
Related
So,
I have two tables, with a 1:M relationship. They have in common two primary keys: tenant and user_id.
I have defined the model relationship and btw, I am not sure if I did it correctly because I am still not sure how to handle composite primary keys on Sequelize. This works well with my many other queries, and I think it influences the problem.
// Sequelize model set-up:
const user = serviceLayerDB.define('user',
{ // Database columns:
tenant: {
type: Sequelize.STRING(45),
primaryKey: true
},
user_id: {
type: Sequelize.STRING(24),
primaryKey: true
},
status: {
type: Sequelize.STRING(11)
}
});
const user_component = serviceLayerDB.define('user_component',
{ // Database columns:
tenant: {
type: Sequelize.STRING(45),
primaryKey: true
},
user_id: {
type: Sequelize.STRING(24),
primaryKey: true
},
component_id: {
type: Sequelize.STRING(24),
primaryKey: true
},
active: {
type: Sequelize.BOOLEAN
}
});
// Sequelize relationship set-up:
user.hasMany(user_component, { foreignKey: 'user_id' });
user.hasMany(user_component, { foreignKey: 'tenant' });
BUT the problem comes when I have the following query:
// Retrieving user and related components.
function getSubscriptions() {
let options = {
where: {
tenant: 'company_A',
user_id: '1001'
},
include: [{ // Adding components, filtered by "active" value:
model: user_component,
where: {
active: 1
},
required: false
}]
};
user.findAll(options)
.then(function(data) {
if (data.length === 0) { // If no data found:
console.log('No data found');
return;
}
// Curate Sequelize result:
let curatedData = data.map(function(userInstance) { return userInstance.get({ plain: true}) }); // Workaround to be able to perform .get().
console.log(JSON.stringify(curatedData, null, 2));
})
.catch(function(error) {
console.log('critical', 'Failed to find data in database. Error: ' + error);
})
}
// Execute:
getSubscriptions();
What I want is to find the user and its components, but only the ones with the active value set to 1. It is not working: the result is every component with the value active set to 1 under the same "tenant", the child include is ignoring the "user_id" that we indicated in the parent.
Am I right to think this is related to my composite primary key? How to fix this in the most elegant manner?
You have to use aliases when you associate a model to another model more then once.
For instance:
user.hasMany(user_component, { foreignKey: 'user_id', as: 'UserComponents' });
user.hasMany(user_component, { foreignKey: 'tenant', as: 'TenantComponents' });
And afterwards you should decide for what exact association you wish to do an include operation:
the association by user_id field
let options = {
where: {
tenant: 'company_A',
user_id: '1001'
},
include: [{ // Adding components, filtered by "active" value:
model: user_component,
as: 'UserComponents'
where: {
active: 1
},
required: false
}]
};
the association by tenant field
let options = {
where: {
tenant: 'company_A',
user_id: '1001'
},
include: [{ // Adding components, filtered by "active" value:
model: user_component,
as: 'TenantComponents'
where: {
active: 1
},
required: false
}]
};
If you would like both child collections with the active: 1 condition you can do this:
let options = {
where: {
tenant: 'company_A',
user_id: '1001'
},
include: [{ // Adding components, filtered by "active" value:
model: user_component,
as: 'UserComponents'
where: {
active: 1
},
required: false,
separate: true
}, { // Adding components, filtered by "active" value:
model: user_component,
as: 'TenantComponents'
where: {
active: 1
},
required: false,
separate: true
}]
};
Please pay attention to separate: true option: this option tells sequielize to do separate queries for childs.
If you wish to get not all users but those ones with active only components (which ones: through user_id or tenant field?) you should set required: true in include. But in this case don't include both associations with required: true. This leads to miltiplication of amount of records in the result SQL query and consumes much more memory.
I have 2 Models, User and Post. I want to be able to get User information when querying a post, and be able to get all of a User's posts when querying a user.
they have an association as follows:
User.hasMany(Post, {
foreignKey: 'user',
as: 'posts'
});
Post.belongsTo(User, {
foreignKey: 'id',
sourceKey: 'user',
as: 'userObject'
})
Post.addScope('defaultScope', {
include: [{ model: User, as: 'userObject' }],
}, { override: true })
User.addScope('defaultScope', {
include: [{ model: Post, as: 'posts' }],
}, { override: true })
Here are my Models
User.js
module.exports.userType = new GQL.GraphQLObjectType({
name: 'User',
fields: () => {
const { postType } = require('../Post/Post');
return {
id: {
type: new GQL.GraphQLNonNull(GQL.GraphQLString),
description: 'user unique id'
},
ci_username: {
type: new GQL.GraphQLNonNull(GQL.GraphQLString),
unique: true,
description: 'case INSENSITIVE username of the user'
},
username: {
type: new GQL.GraphQLNonNull(GQL.GraphQLString),
description: 'case SENSITIVE username of the user'
},
password: {
type: new GQL.GraphQLNonNull(GQL.GraphQLString),
description: 'password for the user'
},
first_name: {
type: new GQL.GraphQLNonNull(GQL.GraphQLString),
description: 'first name of user'
},
last_name: {
type: GQL.GraphQLString,
description: 'last name of user (optional)'
},
profile_picture: {
type: GQL.GraphQLString,
description: 'profile picture for the user'
},
posts: {
type: GQL.GraphQLList(postType),
description: 'list of users posts'
}
}
},
})
/** define User model for the database */
module.exports.User = db.define('user', {
id: {
type: Sequelize.UUID,
primaryKey: true,
unique: true,
},
ci_username: {
type: Sequelize.STRING,
unique: true,
},
username: Sequelize.STRING,
password: Sequelize.STRING,
first_name: Sequelize.STRING,
last_name: Sequelize.STRING,
profile_picture: Sequelize.STRING,
}, {
// Tells sequelize not to query the "CreatedAt" or "UpdatedAt" Columns
timestamps: false
})
Post.js
module.exports.postType = new GQL.GraphQLObjectType({
name: 'Post',
fields: () => {
const { userType } = require('../User/User');
return {
id: {
type: new GQL.GraphQLNonNull(GQL.GraphQLString),
description: 'post unique id'
},
name: {
type: new GQL.GraphQLNonNull(GQL.GraphQLString),
description: 'name of the post'
},
user: {
type: userType,
description: 'user object of who created the post'
},
created_at: {
type: new GQL.GraphQLNonNull(GQL.GraphQLString),
description: 'the datetime the post was created',
}
}
},
})
/** define User model for the database */
module.exports.Post = db.define('post', {
id: {
type: DataTypes.UUID,
primaryKey: true,
unique: true,
},
name: DataTypes.STRING,
user: {
type: DataTypes.STRING,
references: {
model: 'users',
key: 'id'
}
},
created_at: {
type: DataTypes.TIME,
defaultValue: DataTypes.NOW
}
}, {
// Tells sequelize not to query the "CreatedAt" or "UpdatedAt" Columns
timestamps: false
})
Here are my Queries:
allUsers.js
const allUsers = {
type: new GQL.GraphQLList(userType),
args: {
username: {
description: 'username of the user',
type: GQL.GraphQLString,
},
// An arg with the key limit will automatically be converted to a limit on the target
limit: {
type: GQL.GraphQLInt,
default: 10
},
// An arg with the key order will automatically be converted to a order on the target
order: {
type: GQL.GraphQLString
}
},
// use graphql-sequelize resolver with the User model from database
resolve: resolver(User)
}
allPosts.js
const allPosts = {
type: new GQL.GraphQLList(postType),
args: {
username: {
description: 'username of the user',
type: GQL.GraphQLString,
},
// An arg with the key limit will automatically be converted to a limit on the target
limit: {
type: GQL.GraphQLInt,
default: 10
},
// An arg with the key order will automatically be converted to a order on the target
order: {
type: GQL.GraphQLString
}
},
// use graphql-sequelize resolver with the Post model from database
resolve: resolver(Post)
}
I'm currently getting a Maximum call stack size exceeded. I assume because the resolver in the queries are recursively getting details on posts and users infinitely.
Does anyone know of any way to put a depth limitation on the resolver? Or is it just not possible to have a recursive query like this?
You would have to remove the default scope from the included model as shown here like this:
Post.addScope('defaultScope', {
include: [{ model: User.scope(null), as: 'userObject' }],
}, { override: true })
User.addScope('defaultScope', {
include: [{ model: Post.scope(null), as: 'posts' }],
}, { override: true })
To support additional depth, you'd need to implement resolvers for the fields in question, for example:
function resolve (user) {
if (user.posts) {
return user.posts
}
return user.getPosts()
}
I want to create an experience array in User model with new data, and the problem is that I don't get saved data in exec function so I can push new data in array on frontend. This is what I got so far.
router.post('/:username/experience', function(req, res) {
const username = req.params.username;
User.findOneAndUpdate(
username, {
$push: {
experience: req.body
}
}, {
safe: true,
upsert: true
})
.exec(function (err, data) {
console.log(data, "------>");
});
})
This is my schema for experience, which is called in User model like experience: [ExperienceSchema].
const ExperienceSchema = new Schema({
title: {
type: String,
required: true
},
company: {
type: String,
required: true
},
from: {
type: Date,
},
to: {
type: Date,
},
workingNow: {
type: Boolean,
default: false
},
description: {
type: String
}
}, {
usePushEach: true
})
Since findOneAndUpdate returns the original document (state before update) you need to add new: true to the options in order to get the updated document.
options:
{
safe: true,
upsert: true,
new: true
}
I suspect my issue is something silly, but I can't find the error. I'm using mongodb to persist user data. Every seems to work fine but when I look the database I have id: null in every user record.
I don't want that id: null, I already have _id field.
$sails --version
0.11.0
My userController:
module.exports = {
create: function(req, res) {
User.create(req.params.all(), function userCreated(err, user) {
if(err) res.json(401, err);
res.json(200, user);
})
}
}
My user model:
module.exports = {
attributes: {
name: {
type: 'string',
required: true
},
email: {
type: 'string',
email: true,
required: true,
unique: true
},
password: {
type: 'string',
minLength: 6,
maxLength: 15,
columnName: 'encrypted_password',
required: true
},
toJSON: function() {
var obj = this.toObject();
delete obj.password;
return obj;
}
},
beforeCreate: function(values, next) {
require('bcrypt').hash(values.password, 10, function passwordEncrypted(err, encryptedPassword) {
if(err) console.log(err);
values.password = encryptedPassword;
next();
});
}
};
When I create a user from url like http://localhost:1337/user/create?name=theUser&email=user#mail.com&password=123456&role=admin everythink seems to be fine but in my mongodb I see this: (id: null)
{
name: "theUser",
email: "user#mail.com",
role: "admin",
id: null,
createdAt: ISODate("2015-04-27T18:34:42.678Z"),
updatedAt: ISODate("2015-04-27T18:34:42.678Z"),
encrypted_password: "$2a$10$iNt/OR8XhjijqRjkpoNW/eR70HTSDgVJ2WmNppqab79rZt213aywm",
_id: ObjectId("553e81429255e51f419a8ffc")
}
I'd tried with autoPK: false but nothing happens.
Thanks in advance.
As stated in waterline docs,
By default an attribute named id will be automatically added to your
model which will contain an auto-incrementing number unique to each
record. This will be your model's primary key and will be indexed when
available. You can override this if you would like to define your own
primary key factory or attribute.
primaryKey
Will set the primary key of the record. This should be used when autoPK is set to false.
attributes: {
uuid: {
type: 'string',
primaryKey: true,
required: true
}
}
I've been trying a simple 'one way association' on v0.9.16 and later v.10.0.rc8.
I'm guessing, I'm doing something wrong.
I have two collections on my MongoDB and I have two models and a controller such as following;
my User.js file;
module.exports = {
connection: 'mongo',
attributes: {
username: {
type: 'string',
required: true
},
encryptedPassword: {
type: 'string' ,
required: true
},
//one way association
personel: {
model: 'Personel'
}
}
};
my Personel.js file;
module.exports = {
connection: 'mongo',
attributes: {
name: 'string',
phone: {
mobile: 'string',
office: 'string'
},
email: 'string',
gender: 'string'
department: {
department_id: 'integer',
department_name: 'string'
},
title: {
title_id: 'integer',
title_name: 'string'
}
}
};
my UserController.js file;
module.exports = {
show : function (req, res) {
User
.findOne(req.param('id'))
.populate('personel')
.exec(function(err, user) {
console.log(user);
});
}
Console writes 'undefined' for personel field;
{ username: 'jkidd',
personel_id: '1',
personel: undefined }
Edit
To provide association I've just added following codes to my User model
//one way association
personel: {
model: 'Personel'
}
and I'm trying to
show : function (req, res) {
User
.findOne(req.param('id'))
.populate('personel')
.exec(function(err, user) {
if(err) return res.json(err, 400);
if(!user) return res.json({ error: 'No User with that Id' }, 404);
console.log(user);
res.view({
user : user
});
});
}
You missed the Association in your Personel.js:
module.exports = {
connection: 'mongo',
attributes: {
department_id: 'integer',
department_name: 'string',
owner: {
model: "User"
}
},
}
See for more Info: http://beta.sailsjs.org/#/documentation/reference/Models/Associations/OnetoOne.html
Hint: You dont need to config the _id - waterline handle that for you!
Edit:
Also a Issue
And you cannot use "nested attributes" (see: How to translate models in Sails.js?). Change that also!
edit: you almost there... ;-)
module.exports = {
attributes: {
username: {
type: 'string',
required: true
},
encryptedPassword: {
type: 'string' ,
required: true
},
//one way association
personel: {
model: 'user'
},
},
};
and your personel.js
module.exports = {
attributes: {
name: 'string',
email: 'string',
gender: 'string',
owner: {
model: 'user',
},
},
};
I wanted to add a comment but cannot as my rep isn't high enough. As already stated. You missed the Association in your Personel.js.
With this in place waterline should link the models up but it isn't for some reason. I'd suggest removing your id attributes as these are likely confusing matters as you'll get _id added automatically for you which is what waterline will use for the associations.
In your code where you associate the personnel to user do so by setting user.personnel = personnel.id.
If you require more help can you post the code/commands you're using to associate the models too.
[EDIT]
With regards to nested attributes check Sails.js - how to update nested model
Your User model should be as follows:
module.exports = {
attributes: {
username: {
type: 'string',
required: true
},
encryptedPassword: {
type: 'string' ,
required: true
},
//one way association
personel: {
collection: 'personel',
via: 'user',
},
},
};
And your personel model:
module.exports = {
attributes: {
name: 'string',
email: 'string',
gender: 'string',
user: {
model: 'user',
},
},
};
And just a little modification to your controller so that you can view the JSON output in browser:
module.exports = {
show: function (req, res) {
User2.find()
.populate('personel')
.exec(function(err, user) {
console.log(user);
return res.send(200, user);
});
},
}
[EDIT]
I've just realised you weren't talking about a one to many association, the above is that, not a one to one.
Your edited code looks find for the one to one join. How are you creating the models? I think that's your issue, you need something like:
Personel.create({ name: 'personel'}, function(err, createdPersonel) {
User.create({ name: 'user', personel: createdPersonel.id }, function(err, createdUser) {
res.send(200, createdUser )
}
}
That's without error checking and assuming you're in a controller.