Sailsjs. Best way to create (and manage) indexes on sails-mongo (mongodb) - mongodb

I was using sailsjs 0.12. It supported index attributes on models, also
i was using npm package Sails-hooks-mongoat to create inverse indexes and so.
It wasn't ideal, but it worked. Right now they dropped the index attribute and mongoat is currently unsafe and pending updates to work on Sails.js 1.0.
I would like to know the best approach to:
Create Indexes on new deployments.
Migrate (ensure?) indexes on deployment updates.

Since you are not allowed to run 'migrate: alter' in production (even if you try), one option is to create those index in bootstrap file ('config/bootstrap.js').
Imagine you have an User model like this:
var User = {
attributes: {
email : { type: 'string', unique: true, required: true },
username : { type: 'string', unique: true, required: true },
pin: { type: 'string'}
}
};
module.exports = User;
Then you can manually create the missing indexes like this in bootstrap file:
module.exports.bootstrap = async function(done) {
console.log("Loading bootstrap...")
if (process.env.NODE_ENV === 'test') {
}
if (process.env.NODE_ENV === 'production') {
console.log("CREATING DATABASE INDEX BY HAND")
// USER MODEL
var db = User.getDatastore().manager;
await db.collection(User.tableName).createIndex( { email: 1 }, {unique: true} );
await db.collection(User.tableName).createIndex( { username: 1 }, {unique: true} );
// PANDA MODEL
// db = Panda.getDatastore().manager;
// await db.collection(Panda.tableName).createIndex( { name: 1 }, {unique: true} );
}
// await initializeDatabase() // custom DB initialization...
return done();
};
The index are created only once, subsequent runs will not recreate those indexes.
The ensureIndex was an alias to createIndex function and it has been deprecated.
References:
Waterline manager reference
MongoDB create index reference

In development mode you can specify custom indexes in the model within Sails, or it will remove them when lifting the server and performing migration.
My (preferred) alternative approach is to manage all indexes on my own within the DB. In order to do so, you have to change the "migrate" attribute from "alter" to "safe" in models.js config file.
Note that in production mode "migrate" attribute is always set to "safe".

Related

Mongoose set default on field update if field is not present or null

I have a mongoose schema like this suppose:-
var mSchema = new Schema({
name: { type: String, required: true}
});
and have been using this schema for a year and now i want to add gender to it like this :-
var mSchema = new Schema({
name: { type: String, required: true},
gender: { type: String, default: 'Male' }
});
whenever there will be an update request i want this gender to automatically set Male as default but i found that default don't set on update request.
(Note: It's just an example not a real life scenario. i just want mongoose default work if field is not present or null)
Is there any way in which i can set default on the updation of document ?
If you are using a function like update(), then this is not directly possible as stated by this answer. Still, you can simply switch to a function like findOne() and use save(), which should do the same.
When upserting documents, you can also check out the setDefaultsOnInsert option: https://mongoosejs.com/docs/defaults.html#the-setdefaultsoninsert-option
const options = {
// Create a document if one isn't found. Required
// for `setDefaultsOnInsert`
upsert: true,
setDefaultsOnInsert: true
};
await XY.findOneAndUpdate(query, update, options);

How can I use the Find method to perform a case insensitive search on the database field?

I am unable to get the case insensitive search from the database using Sails.js V1.0 + Waterline ORM. I am using sails-postgresql adapter. The running environment is Heroku + Heroku PostgreSQL.
Is there any way to turn off the following setting in database adapter - For performance reasons, case-sensitivity of contains depends on the database adapter.
Tried the method:
Datastore configuration is:
default: {
adapter: 'sails-postgresql',
url: 'postgres://....',
ssl: true,
wlNext: {
caseSensitive: true
}
}
The code block is:
var meetings = await Meeting.find({
select: ['id', 'uid', 'name', 'deleted', 'complete'],
where: {
owner: user.id,
name: { contains: searchJson.name } : ""
},
skip: (inputs.page > 0) ? (inputs.page) : 0,
limit: (inputs.limit > 0) ? (inputs.limit) : 10,
sort: 'date DESC'
});
The easiest way I've found to handle this and case-insensitive unique indexes with PG/Sails is to use the citext column type instead of text/character varying types (compared to forcing everything to lowercase which stinks).
citext is a case insensitive text datatype. "Essentially, it internally calls lower when comparing values. Otherwise, it behaves almost exactly like text."
An example model attribute pulled from a working app:
username: {
type: 'string',
columnType: 'citext',
required: true,
unique: true,
description: 'A users.. Username',
// ...
},
According to this (somewhat irellevant) Heroku docs page this looks like it should work, but you may need to run create extension citext; on your database first.
For MongoDB https://github.com/balderdashy/sails/issues/7014.
From sails-mongo#1.2.0, you can chain on .meta({makeLikeModifierCaseInsensitive: true}) for a case-insensitive query.
Example
await User.find(criteria).meta({makeLikeModifierCaseInsensitive: true});
You can use native queries, for example:
const eventTypesRows = await Meeting.getDatastore()
.sendNativeQuery(\`SELECT "name" FROM meeting WHERE LOWER(name) = LOWER($1)\`, [searchName]);

Sort populated record in sails waterline

I created a Sails application with two models Publication and Worksheet. They are having a one-to-one relationship. Sails-postgresql is the adapter I'm using. I'm using waterline orm to fire query to the database. I'm When I am trying to load publications data along with worksheet and then sort the records depending on a field in the Worksheet using sort() I'm getting an error.
My model is:
Publication.js
module.exports = {
attributes: {
id: {
type: 'integer'
unique: true
},
worksheetId: {
type: 'integer',
model : 'worksheet'
},
status: {
type: 'string',
defaultsTo: 'active',
in : ['active', 'disabled'],
}
}
}
Worksheet.js
module.exports = {
attributes: {
id: {
type: 'integer',
unique: true
},
name: 'string',
orderWeight: {
type: 'integer',
defaultsTo: 0
}
}
}
So now I want to load all the publication where status is "active" and populate worksheet in the data.
So I'm executing the query:
Publication.find({
where: {
status: 'active'
}
})
.populate('worksheetId').limit(1)
.exec(function (error, publications) {
})
And I'm getting a data like :
{
id : 1,
status : "active",
worksheetId : {
id : 1
name : "test",
orderWeight : 10
}
}
So till now it's all working fine. Now I want to increase the limit to 10 and want to sort the data depending on "orderWeight" which is in the populated data. Initially I sorted the whole data depending on publication id and the query worked.
Publication.find({
where: {
status: 'active'
}
})
.populate('worksheetId').sort('id ASC').limit(10)
.exec(function (error, publications) {
})
So I fired similar query to sort the data on "orderWeight"
Publication.find({
where: {
status: 'active'
}
})
.populate('worksheetId').sort('worksheetId.orderWeight ASC').limit(10)
.exec(function (error, publications) {
})
And this query is giving me error that worksheetId.orderWeight is not a column on the publication table. So I want to fire this sort query on the populated data not on the publication table.
Please let me know how I can get my expected result.
Apart from sort() method I also want to run some find command to the populated data to get those publication where the worksheet name matches with certain key as well.
Basically, what you're trying to do, is query an association's attribute. This has been in the waterline roadmap since 2014, but it's still not supported, so you'll have to figure out a workaround.
One option is to query the Worksheet model, and populate the Publication, since sails doesn't let you query across models without using raw queries (i.e. .sort('worksheetId.orderWeight ASC') doesn't work). Unfortunately, you might have to move the active flag to the Worksheet. For example:
Worksheet.find({
status: 'active'
})
.populate('publication') // you should also add publication to Worksheet.js
.sort('orderWeight ASC')
.limit(10)
Alternatively, you could combine Worksheet and Publication into one model, since they're one-to-one. Probably not ideal, but sails.js and Waterline make it very difficult to work with relational data - I'd estimate that half of the queries in the project I'm working on are raw queries due to sails' poor support of postgres. The framework is pretty biased towards using MongoDB, although it claims to "just work" with any of the "supported" DBs.

Sailsjs - Prevent non-model fileds to be saved in mongo document

I recently started working with Sails and mongo.
I use Sails blueprints to generate part of my api.
The problem is, that the request body I send is being saved to the mongo collection, regardless of the fields defined in the model.
So for example, let's say I have the following Event model:
module.exports = {
attributes: {
title: {
type: 'string',
required: true
},
}
}
When I Send a POST request to the /event/ endpoint with the following params:
{"title":"Some Event", "random":"string"}
The saved mongo document contains also the "random":"string" value, even though it's not part of the model.
I've tried to come up with some common method to remove non-model attributes before creation for all models, but the possible solutions seemed not right and dirty.
Am I missing something?
Any help would be appreciated!
You can use schema option in your model. Just add it to model declaration and that's it.
// api/models/Model.js
module.exports = {
schema: true,
attributes: {
title: {
type: 'string',
required: true
}
}
};

How do I specify a field that can be validated but not stored in Waterline?

I'm looking at the example in the Waterline docs here.
var User = Waterline.Collection.extend({
types: {
// snip
password: function(password) {
return password === this.passwordConfirmation;
});
},
attributes: {
// snip
password: {
type: 'string',
password: true
},
passwordConfirmation: {
type: 'string'
}
}
});
Is there a way to tell Waterline that passwordConfirmation is not part of the schema so that it is not created if migrate is set to alter or drop, or if using a schemaless DB engine?
The actual use case I want is for a clear text password field to validated from the request, but use beforeCreate to populate a hash field that is actually stored (but not allowing the password property to be stored in the process).
Thanks.
Waterline doesn't support transient fields that are validated but not persisted. You can add the schema: true property to your model which will have it filter out any attributes that aren't explicitly declared, but still make them available in lifecycle callbacks. You'd have to do the validation for those attributes manually (in beforeCreate() or beforeValidate() for example), and you'd lose the ability to add arbitrary fields to schemaless dbs, but it's not necessarily a bad solution.
For your case though, I don't see why it's exactly necessary. Why not just hash the password in beforeCreate and save it back to password?
beforeCreate: function (values, cb) {
values.password = hash(values.password);
return cb();
}