Global Models in SailsJS not accessible - sails.js

In porting a sails app to 1.x framework. I found that my global model names (which are physically defined in api/models directory) were undefined in one of my hook initialize function and in config/bootstrap.js
"models": true, is defined in config/globals.js in this project.
For example, one of my models is Job.js:
const async = require('async');
const _ = require('lodash');
const fetch = require('node-fetch');
module.exports = {
attributes: {
id: {
type: 'integer',
autoIncrement: false,
unique: true,
primaryKey: true
}
},
Init: function(params,cb) {
sails.log.info('Job Engine Starting');
...
}
}
But, when I try to call Job.Init(), from hooks/index.js or from config/bootstrap.js, I get reference error: Job is undefined.

Related

Asynchronous Issues with JEST and MongoDB

I am getting inconsistent results with JEST when I try to remove items from a MongoDB Collection using the beforeEach() Hook.
My Mongoose schema and model defined as:
// Define Mongoose wafer sort schema
const waferSchema = new mongoose.Schema({
productType: {
type: String,
required: true,
enum: ['A', 'B'],
},
updated: {
type: Date,
default: Date.now,
index: true,
},
waferId: {
type: String,
required: true,
trim: true,
minlength: 7,
},
sublotId: {
type: String,
required: true,
trim: true,
minlength: 7,
},
}
// Define unique key for the schema
const Wafer = mongoose.model('Wafer', waferSchema);
module.exports.Wafer = Wafer;
My JEST tests:
describe('API: /WT', () => {
// Happy Path for Posting Object
let wtEntry = {};
beforeEach(async () => {
wtEntry = {
productType: 'A',
waferId: 'A01A001.3',
sublotId: 'A01A001.1',
};
await Wafer.deleteMany({});
// I also tried to pass in done and then call done() after the delete
});
describe('GET /:id', () => {
it('Return Wafer Sort Entry with specified ID', async () => {
// Create a new wafer Entry and Save it to the DB
const wafer = new Wafer(wtEntry);
await wafer.save();
const res = await request(apiServer).get(`/WT/${wafer.id}`);
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('productType', 'A');
expect(res.body).toHaveProperty('waferId', 'A01A001.3');
expect(res.body).toHaveProperty('sublotId', 'A01A001.1');
});
}
So the error I always get is related to duplicate keys when I run my tests more than once:
MongoError: E11000 duplicate key error collection: promis_tests.promiswts index: waferId_1_sublotId_1 dup key: { : "A01A001.3", : "A01A001.1" }
But I do not understand how I can get this duplicate key error if the beforeEach() were firing properly. Am I trying to clear the collection improperly? I've tried passing in a done element to the before each callback and invoking it after delete command. I've also tried implementing the delete in beforeAll(), afterEach(), and afterAll() but still get inconsistent results. I'm pretty stumped on this one. I might just removed the schema key all together but I would like to understand what is going on here with the beforeEach(). Thanks in advance for any advice.
It might be because you are not actually using the promise API that mongoose has to offer. By default, mongooses functions like deleteMany() do not return a promise. You will have to call .exec() at the end of the function chain to return a promise e.g. await collection.deleteMany({}).exec(). So you are running into a race condition. deleteMany() also accepts a callback, so you could always wrap it in a promise. I would do something like this:
describe('API: /WT', () => {
// Happy Path for Posting Object
const wtEntry = {
productType: 'A',
waferId: 'A01A001.3',
sublotId: 'A01A001.1',
};
beforeEach(async () => {
await Wafer.deleteMany({}).exec();
});
describe('GET /:id', () => {
it('Return Wafer Sort Entry with specified ID', async () => {
expect.assertions(4);
// Create a new wafer Entry and Save it to the DB
const wafer = await Wafer.create(wtEntry);
const res = await request(apiServer).get(`/WT/${wafer.id}`);
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('productType', 'A');
expect(res.body).toHaveProperty('waferId', 'A01A001.3');
expect(res.body).toHaveProperty('sublotId', 'A01A001.1');
});
}
Also, always expect the assertions with asynchronous code
https://jestjs.io/docs/en/asynchronous.html
You can read more about mongoose promises and query objects here
https://mongoosejs.com/docs/promises.html
Without deleting the schema index this seems to be the most reliable solution. Not 100% sure why it works over async await Wafer.deleteMany({});
beforeEach((done) => {
wtEntry = {
productType: 'A',
waferId: 'A01A001.3',
sublotId: 'A01A001.1',
};
mongoose.connection.collections.promiswts.drop(() => {
// Run the next test!
done();
});
});

Typeahead/Bloodhound dupDetector for Multiple Datasets

I have two Bloodhound datasets, a local and a remote. The local data will always be duplicated in the remote results. I've found the dupDetector method mentioned in Typeahead documentation, but it seems this method only works if both my local and remote datasets are built within the same Bloodhound object.
Here's my code. Is there a way to filter duplicates across multiple Bloodhound datasets?
var local_props = new Bloodhound({
datumTokenizer: function (p) {
return Bloodhound.tokenizers.whitespace(p.name);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: portfolio_props,
limit: 100
});
var remote_props = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: 'properties/searchPropertiesLike?substr=%QUERY',
dupDetector: function(remoteMatch, localMatch) {
return remoteMatch.name === localMatch.name;
},
limit: 100
});
local_props.initialize();
remote_props.initialize();
$('#typeahead-property').typeahead({
hint: true,
highlight: true,
minLength: 2
},
{
name: 'port_properties',
displayKey: 'name',
source: local_props.ttAdapter(),
templates: {
header: '<h3>Your Portfolio Properties</h3>'
}
},
{
name: 'dir_properties',
displayKey: 'name',
source: remote_props.ttAdapter(),
templates: {
header: '<h3>Properties Directory</h3>'
}
});

Sails.JS association validation

I'm using several one to many associations in Sails.JS that look like the following:
User
email: {
type: 'string',
required: true,
unique: true
},
projects: {
collection: 'project',
via: 'user'
}
Project
name: {
type: 'string',
required: true,
minLength: 3,
maxLength: 50
},
user: {
model: 'user',
required: true
},
sites: {
collection: 'site',
via: 'project'
}
Site
project: {
model: 'project',
required: true
},
name: {
type: 'string',
required: true
}
Now when I fire off a POST request to /project it creates the project fine, and specifying the param 'user' (taken from the session) associates the project with that particular user.
The same goes for when I create a new site. However, I appear to be able to specify any number for the param 'project', even if that particular project ID doesn't exist. Really it should fail the validation if the project doesn't exist and not create the site. I thought it'd look up the association with project and check that the project ID specified is valid?
Also, I only want to be able to create a site that is associated with a project that belongs to the current user. How would I go about doing this?
Thanks in advance.
I'm not sure if it's a bug or intended behavior with your non-existent project ID association, but one work-around is to have a beforeCreate hook in your models to verify that the project ID exists:
// In your Site model
beforeCreate: function(values, next) {
...
var projectID = values['project'];
Project.findOne(projectID, function (err, project) {
if (err || !project) return next("some error message");
return next();
});
}
You can also do a check in the beforeCreate hook for your second question:
// In your Site model
beforeCreate: function(values, next) {
...
var projectID = values['project'];
Project.findOne(projectID).populate('user').exec(function (err, project) {
if (err || !project) return next("some error message");
if (project.user.id != values['userID']) return next("some other error message");
return next();
});
}
Note that you'll have to pass 'userID' as a param into the params for creating a Site instance.

Avoiding global leaking when using JayData 1.3.4 local item store

Out of the box Entities defined by using $data.Entity.extend will be globally accessible. e.g. in the example taken from JayData's home page Todo will leak.
// Case 1: local item store example from http://jaydata.org/
$data.Entity.extend("Todo", {
Id: { type: "int", key: true, computed: true },
Task: { type: String, required: true, maxLength: 200 },
DueDate: { type: Date },
Completed: { type: Boolean }
});
console.log('Leaks Todo?', typeof window.Todo !== 'undefined');
//Result: true
In a JayData forum post I found a reference to $data.createContainer(), which can be used as container during Entity definition. In this case Todo2 won't leak.
// Case2: creating Todo2 in a container
$data.Entity.extend("Todo2", container, {
Id: { type: "int", key: true, computed: true },
Task: { type: String, required: true, maxLength: 200 },
DueDate: { type: Date },
Completed: { type: Boolean }
});
console.log('Leaks Todo2?', typeof window.Todo2 !== 'undefined');
//Result: false
Unfortunately after accessing stores there'll be other variables that leak globally even if the Entity itself is associated with a container.
console.log('Before store access: Leaks Todo2_items?',
typeof window.Todo2_items !== 'undefined');
//Result: false
$data('Todo2').save({ Task: 'Initialized Todo2'})
console.log('After store access: Leaks Todo2_items?',
typeof window.Todo2_items !== 'undefined');
//Result: true
Complete fiddle can be found at http://jsfiddle.net/RainerAtSpirit/nXaYn/.
In an ideal world every variable that is created for entities that run in a container would be associated with the same container. Is there an option to accomplish that or is the behavior described in Case2 the best that can be currently accomplished?

How to implement ReST services with Sails.js?

I am quite new to Node. I came across Sails.js. I think it is based on WebSocket, which seems to be really good for building real-time applications. I would like to know that whether Sails can be used to implement REST architecture as it uses WebSocket? And if yes, how?
Yes it can. Sails JS allows you to easily build a RESTful API, essentially with no effort to get started. Also, websockets (through socket.io) are integrated by default into the view and api.
To create a fully RESTful app from the ground up, it actually requires no JS. Try:
sails new testapp
cd testapp
sails generate model user
sails generate controller user
cd <main root>
sails lift
The CRUD (Create, Read, Update, Delete) actions are already created for you. No code!
You can create a user in your browser by doing the following:
HTTP POST (using a tool like PostMan) to http://:1337/user/create
{
"firstName": "Bob",
"lastName": "Jones"
}
Next, do a GET to see the new user:
HTTP GET http://:1337/user/
FYI - Sails JS uses a default disk based database to get you going
Done.
sails new testapp
cd testapp
sails generate api apiName
controller
create: function (req, res) {
var payload = {
name:req.body.name,
price:req.body.price,
category:req.body.category,
author:req.body.author,
description:req.body.description
};
Book.create(payload).exec(function(err){
if(err){
res.status(500).json({'error':'something is not right'})
}else{
res.status(200).json({'success':true, 'result':payload, 'message':'Book Created success'})
}
});
},
readone: async function (req, res) {
var id = req.params.id;
var fff = await Book.find(id);
if(fff.length == 0){
res.status(500).json({'error':'No record found from this ID'})
}else{
res.status(200).json({'success':true, 'result':fff, 'message':'Record found'})
}
},
model
attributes: {
id: { type: 'number', autoIncrement: true },
name: { type: 'string', required: true, },
price: { type: 'number', required: true, },
category: { type: 'string', required: true, },
author: { type: 'string' },
description: { type: 'string' },
},
routes
'post /newbook': 'BookController.create',
'get /book/:id': 'BookController.readone',