How to set Primary keys in Sails js waterline database relationships - node.js

I have been studying relationships with sails JS waterline database from the official documentation. I however been having difficulty understanding how I am supposed to set my foreign keys just like I do in normal mysql relationships.
Please note that I have read the documentation here https://sailsjs.com/documentation/concepts/models-and-orm/associations before asking this question.
Let's say I have a model PersonalInfo.js
module.exports = {
attributes: {
fullName:{
type: 'string',
required: true
},
phone:{
type: 'string',
required: true
},
location:{
type: 'string',
required: true
},
age:{
type: 'integer',
required: true
},
email:{
type: 'string',
required: true
},
gender:{
type: 'string',
required: true
},
userId:{
type: 'integer',
required: true,
}
},
};
And I have another model Archived.js which looks like this
module.exports = {
attributes: {
userId: {
type: 'number',
required: true,
//unique: true,
},
comment:{
type: 'string',
required: true
},
createdBy:{
type: 'number',
required: true
}
},
};
An archived item has a personalInfo. Knowing fully well that both models contain userId property, I want to fetch archived items with the related personalInfo like this, how do I relate the primary keys?
var archived = Archived.find().populate('personal');

By default sails will generate primary key id if you don't specify any.
If you want custom data as your primary key, you can override the id attribute in the model and give a columnName
id: {
type: 'string',
columnName: 'email_address',
required: true
}
You can then find a record using:
await User.find({ id: req.param('emailAddress' });
Reference
In your case, it seems like each archived has a personalInfo. So that's one to one from archived side, but, one to many from personalInfo side. To model these relationships, in sails you can do something like:
personalInfo.js
module.exports = {
attributes: {
fullName:{
type: 'string',
required: true
},
phone:{
type: 'string',
required: true
},
location:{
type: 'string',
required: true
},
age:{
type: 'integer',
required: true
},
email:{
type: 'string',
required: true
},
gender:{
type: 'string',
required: true
},
userId:{
type: 'integer',
required: true,
},
archives: {
collection: 'archived',
via: 'info'
}
},
};
archived.js
module.exports = {
attributes: {
userId: {
type: 'number',
required: true,
//unique: true,
},
comment:{
type: 'string',
required: true
},
createdBy:{
type: 'number',
required: true
},
info: {
model: 'personalinfo' // sails works with small cases internally for models
}
},
};
Once you do this, creating an archive would be:
await Archive.create({
...
// Set the User's Primary Key to associate the info with the archive.
info: 123
});
Now you would finally be able to populate the info while querying.
var archived = Archived.find().populate('info');

Related

MongoDB Many to many relationship reference with extra fields

I have 2 models - User & Project.
Relations are -
Users can have multiple projects (One to many)
One projects may be shared with multiple other users. (Many to many)
const UserSchema = new Schema({
name: {
type: String,
required: true,
},
mobile: {
type: Number,
required: true,
unique: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true,
},
role: {
type: String,
default: 'basic',
enum: ["basic", "admin"]
},
projects: [
{
type: Schema.Types.ObjectId,
ref: 'Project'
}
],
shared_projects: [
{
type: Schema.Types.ObjectId,
ref: 'Project'
}
]
}, { timestamps: true });
const ProjectSchema = new Schema({
name: {
type: String,
required: true,
},
active: {
type: Boolean,
default: false,
},
expiry: {
type: Date,
default: null,
},
owner: {
type: Schema.Types.ObjectId,
ref: 'User'
},
shared_users: [
{
type: Schema.Types.ObjectId,
ref: 'User'
}
]
}, { timestamps: true });
I want to have a feature that the owner of the project may disable one or many shared user. So, I need a status field in ProjectSchema like -
shared_users: [
{
type: Schema.Types.ObjectId,
ref: 'User'
},
status: {
type: Boolean,
default: true
}
]
So, do I need to have the same definition in UserSchema also & update both schemas when user disable or enable a shared user?

Mongoose Subdocument for Geojson

I am not able to define a correct reusable Point Schema. I just copied the example schema in https://mongoosejs.com/docs/geojson.html
This is the error I'm encountering when starting the node.js app
/home/******/projects/realista-api/node_modules/mongoose/lib/schema.js:418
throw new TypeError('Invalid value for schema path ' + prefix + key + '');
^
TypeError: Invalid value for schema path coordinates
I already tried using a non-reusable schema. By directly defining it at the parent schema and it works
coordinates: {
type: {
type: String,
enum: ['Point'],
required: true
},
coordinates: {
type: [Number],
required: true
}
},
Here is the code
import { Schema, Document } from 'mongoose';
interface Point extends Document {
type: string,
coordinates: Array<number>,
}
const PointSchema: Schema = new Schema({
type: {
type: String,
enum: ['Point'],
required: true
},
coordinates: {
type: [Number],
required: true
}
}, {
id: false
});
export {
Point,
PointSchema,
}
I'm using that as subdocument in another schema
const ProjectSchema: Schema = new Schema({
owner: {
type: Schema.Types.ObjectId,
ref: 'User',
required: false,
},
logo: {
type: String,
required: false,
},
name: {
type: String,
required: true,
},
location: {
type: String,
required: false,
},
suburb: {
type: String,
required: false,
},
stateCode: {
type: String,
required: false,
},
country: {
type: String,
required: false,
},
countryName: {
type: String,
required: false,
unique: true,
sparse: true,
},
coordinates: PointSchema,// this is the field in question
commission: {
type: Schema.Types.Decimal128,
required: false,
},
tax: {
type: Schema.Types.Decimal128,
required: false,
},
propertyType: {
type: Schema.Types.ObjectId,
ref: 'PropertyType',
required: true,
},
address: {
type: String,
required: false,
},
title: {
type: String,
required: false,
},
description: {
type: String,
required: false,
},
videoTour: {
type: String,
required: false,
},
matterPortUrl: {
type: String,
required: false,
},
currency: {
type: String,
required: false,
},
minPrice: {
type: Schema.Types.Decimal128,
required: false,
},
maxPrice: {
type: Schema.Types.Decimal128,
required: false,
},
otherPrice: {
type: String,
required: false,
},
featureLandSizeMin: {
type: String,
required: false,
},
featureLandSizeMax: {
type: String,
required: false,
},
featureLandSizeUnit: {
type: String,
required: false,
},
featureBuiltStart: {
type: Date,
required: false,
},
featureBuiltEnd: {
type: Date,
required: false,
},
featureNumOfLevel: {
type: Number,
required: false,
},
featureNumOfUnit: {
type: Number,
required: false,
},
featureFlooring: {
type: String,
required: false,
},
featureExterior: {
type: String,
required: false,
},
featureConcierge: {
type: String,
required: false,
},
indoorFeatures: {
type: String,
required: false,
},
outdoorFeatures: {
type: String,
required: false,
},
minBedrooms: {
type: Number,
required: false,
},
maxBedrooms: {
type: Number,
required: false,
},
minBathrooms: {
type: Schema.Types.Decimal128,
required: false,
},
maxBathrooms: {
type: Schema.Types.Decimal128,
required: false,
},
minParking: {
type: Number,
required: false,
},
maxParking: {
type: Number,
required: false,
},
csvVariationPending: {
type: Boolean,
required: false,
default: false,
},
isPrivate: {
type: Boolean,
required: false,
default: false,
},
status: {
type: Boolean,
required: false,
default: false,
},
variations: [{
type: Schema.Types.ObjectId,
ref: 'ProjectVariation',
}],
deletedAt: {
type: Date,
required: false,
}
}, {
collection: 'projects',
timestamps: true,
strict: false,
});
What am I doing wrong? Thanks in advance.
Was able to make it work. Hope this helps others who are developing using node.js
The issue was caused by 2 things:
Mongoose, when declaring subdocuments (nested objects or array of objects), gets confused when there is a type field in the document since in Mongoose concept, it is a reserved word for declaring the type of field. In my case, the type key comes from GeoJSON since it is required by MongoDB to be in that format. Here is a link from mongoose docs for better understanding.
What I just did is change PointSchema to this
import { Schema, Document } from 'mongoose';
interface Point extends Document {
type: string,
coordinates: Array<number>,
}
const PointSchema: Schema = new Schema({
type: {
$type: String,
enum: ['Point'],
required: true
},
coordinates: {
$type: [Number],
required: true
}
}, {
_id: false,
typeKey: '$type',
});
export {
Point,
PointSchema,
}
I'm also having a problem w/ circular dependency in node.js when importing/requiring the PointSchema. This error
/home/******/projects/realista-api/node_modules/mongoose/lib/schema.js:418 throw new TypeError('Invalid value for schema path ' + prefix + key + ''); ^ TypeError: Invalid value for schema path coordinates
occurred because PointSchema is undefined when I'm using it in ProjectSchema.
W/c justifies why issues in Mongoose Github suggest that mostly when they encounter that error, it is because of misspelled types (ObjectID instead of ObjectId), or in my case undefined w/c is invalid type.

Pass a hardcoded value through mongoose schema

I would like to post some hard coded values along with user input(variables) every time.
args: [{ type: mongoose.Schema.Types.Mixed, required: true }] >>in this array i would like to pass some hard coded values along with user input variables.
Well my data to be posted looks like this.
{"file": "**<user input>**","name":"<user input>", "className": "com.izac.Parser.IO", "args": ["-i", "{\"enrichedKafkaOptions\": {\"checkpointLocation\": \"**<hard coded path always remains the same>**", \"kafka.bootstrap.servers\": \"localhost:9092\", \"topic\": \"demoEnriched\"}, \"rejectedKafkaOptions\": {\"checkpointLocation\": \"/Users/vipulrajan/Desktop/checkpoints/DemoRejected\", \"kafka.bootstrap.servers\": \"localhost:9092\", \"topic\": \"demoRejected\"} }","-s", "{\"master\":\"local[*]\", \"appName\":\"app1\", \"config\":{\"jvm.memory\":\"4g\"} }"]};
This is my schema,
const mongoose = require('mongoose');
const livy_schema = mongoose.Schema({
file: { type: String, required: true },
name: { type: String, required: true },
className: { type: String, required: true },
args: [{ type: mongoose.Schema.Types.Mixed, required: true }] //here i have constants to pass on to
});
const kafka_schema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true, unique: false },
config: { type: mongoose.Schema.Types.Mixed, required: true } //here i have constants to pass on to
});
const enrichedEventSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
projectId: { type: mongoose.Schema.Types.ObjectId, ref: 'Project', required: true },
name: { type: String, required: true, unique: true },
description: { type: String, required: false },
type: { type: String, enum: ["Enriched"], required: true },
format: { type: String, enum: ["JSON", "DELIMITED", "FixedWidth", "LOG"], required: true },
kafka: [kafka_schema],
livy: [livy_schema]
});
Original question Asynchronous Programming in node js to pass constants/predefined mandatory values through mongoose model .
I'm kind of in dilemma, like should i pass this hardcodeded values in router.post() method(if its possible, how should i code?) or in schema? please guide me in right direction.
Please excuse me if I am misunderstanding the question.
Since you are using mongoose schema you can have your field default be a function where you can initialize and add hardcoded values.
Something like this:
const livy_schema = mongoose.Schema({
file: {
type: String,
required: true
},
name: {
type: String,
required: true
},
className: {
type: String,
required: true
},
args: [{
type: mongoose.Schema.Types.Mixed,
required: true,
default: function() {
return { data: 'hardcoded!', info: 'hardcoded!' }
}
}] //here i have constants to pass on to
});
)
if the schema is in the right context I assume you can easily replace those strings with values being passed or swap the default function.

How to keep track of videos watched with Node.js and Mongodb

I'm building a MEAN stack video app (I'm pretty new to Node and Mongodb) and I need a way to keep track of videos watched. How do I do this?
I was thinking I could have an array of Ids in the user collection that references videos but I'd like to be able to return videos with a watched: true key value pair that's dependent on the user making the request. If this is a good way to do it, how do I return a key value pair that's dependent on another document in another collection?
User model:
let UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
minlength: 1,
trim: true,
unique: true,
validate: {
validator: VALUE => validator.isEmail(VALUE),
message: '{VALUE} is not a valid email'
}
},
password: {
type: String,
required: true,
minlength: 6
},
admin: {
type: Boolean,
default: false
},
vid_inprogress: {
type: mongoose.Schema.Types.ObjectId
},
vid_completed: [{ type : mongoose.Schema.Types.ObjectId, ref: 'Attachment' }],
tokens: [{
access: {
type: String,
required: true
},
token: {
type: String,
required: true
}
}]
});
Video Model:
var Video = mongoose.model('Video', {
url: {
type: String,
required: true,
minlength: 1,
trim: true
},
title: {
type: String,
required: true,
default: '',
trim: true
},
description: {
type: String,
default: '',
trim: true
},
img: {
type: String,
default: '',
trim: true
},
attachments: [{ type : mongoose.Schema.Types.ObjectId, ref: 'Attachment' }]
});
vid_completed on the User model is where I'd like to keep track of the video ids that have been watched. And the Video model is what would be returned with a key: value pair based on whether the video id is found in the user vid_completed array. Let me know if that makes sense. Thanks in advance!
I ended up just using an array in the User model like so:
vid_inprogress: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Video'
},
vid_completed: [{ type : mongoose.Schema.Types.ObjectId, ref: 'Video' }]
I'll just have to add watched: true on the front end. Let me know if you have a better way. I'd be interested to hear.

Query in Waterline using a model as a criteria

I just started to work with Waterline and I got question about search for records in a database Mongo using a model as criteria. After some hours of search I couldn't find any satisfactory solution.
First, I have basically 2 model related between themselves:
Post.js
var Post = Waterline.Collection.extend({
tableName: 'Post',
connection: 'default',
attributes: {
url : { type: 'string', required: true, unique: true, lowercase: true },
title : { type: 'string', required: true },
body : { type: 'string', required: true },
author : { type: 'string', required: true },
writeIn : { type: 'string', required: true },
tags: {
collection: 'Tag',
via: 'posts',
dominant: true
},
category: {
model: 'Category'
}
}});
Category.js
var Category = Waterline.Collection.extend({
tableName: 'Category',
connection: 'default',
attributes: {
url: { type: 'string', required: true, unique: true, lowercase: true },
name: { type: 'string', required: true },
posts: {
collection: 'Post',
via: 'category'
}
}});
They are related using Many-to-Many association. The point is that I would like to query a list of posts through a category name.
Something like this:
Post.find().where({category: {url: 'java'}})
Does any of you know how to do this?
Yes you can do this by different way...
Category.find()
.where({url: 'java'})
.populate('posts')

Resources