locomotivejs and mongoose: passing promise variables to a controller - node.js

preamble:
I'm not sure if this is the best way to ask this question as I'm fairly sure it's more general than I'm making it and there's probably a global pattern that address my concern.
For the moment here's the question in the context of the original code that I'm working with.
Given a locomotivejs controller, let's call it Contact_Controller, with general structure like this:
'\controllers\contact_controller.js
var locomotive = require('locomotive')
, Controller = locomotive.Controller
, Contact = require('../models/contact')
, sender = require('../helpers/sender')
;
var Contact_Controller = new Controller();
and then:
Contact_Controller.list = function() {
//Provides a list of all current (successful) contact attempts
var controller = this;
Contact.find({status: 1}, function(err, results) {
if(err) {
controller.redirect(controller.urlFor({controller: "dashboard", action: "error"}));
}
controller.contacts = results;
controller.render();
});
};
and the model:
'/models/contact.js
var mongoose = require('mongoose')
, mongooseTypes = require('mongoose-types')
, pass = require('pwd')
, crypto = require('crypto')
, Schema = mongoose.Schema
, Email = mongoose.SchemaTypes.Email;
var ContactSchema = new Schema({
email: {type: Email, required: true},
subject: {type: String, required: true },
message: { type: String, required: true},
status: {type: Number, required: true, default: 1},
contact_time: {type: Date, default: Date.now}
});
module.exports = mongoose.model('contact', ContactSchema);
Inside the list action of the contact_controller I'd really rather not use controller = this; I generally prefer using redirect = this.redirect.bind(this); style localized binding to handle these sort of situations.
However, I can't think of a way to return the results to the controller's this object without creating a global variable version of this and having the promise's callback talk to that. Is there a better way to return the results variable or expose the contact_controller object in this context?

Do you mean this?
Contact.find({status: 1}, function(err, results) {
if (err) {
this.redirect(this.urlFor({this: "dashboard", action: "error"}));
return; // you should return here, otherwise `render` will still be called!
}
this.contacts = results;
this.render();
}.bind(this));
^^^^^^^^^^^ here you bind the controller object to the callback function

Related

Autoincrement with Mongoose

I'm trying to implement an autoicremental user_key field. Looking on this site I came across two questions relevant for my problem but I don't clearly understand what I should do. This is the main one
I have two Mongoose models, this is my ProductsCounterModel.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var Counter = new Schema({
_id: {type: String, required: true},
sequence_value: {type: Number, default: 0}
});
module.exports = mongoose.model('products_counter', Counter);
and this is the Mongoose model where I try to implement the auto-increment field:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var products_counter = require('./ProductsCounterModel.js');
var HistoricalProduct = new Schema({
product_key: { type: Number },
class: { type: String },
brand: { type: String },
model: { type: String },
description: { type: String }
});
HistoricalProduct.pre("save", function (next) {
console.log("first console log:",products_counter);
var doc = this;
products_counter.findOneAndUpdate(
{ "_id": "product_key" },
{ "$inc": { "sequence_value": 1 } },
function(error, products_counter) {
if(error) return next(error);
console.log("second console log",products_counter);
doc.product_key = products_counter.sequence_value;
next();
});
});
module.exports = mongoose.model('HistoricalProduct', HistoricalProduct);
Following the steps provided in the above SO answer I created the collection products_counter and inserted one document.
The thing is that I'm getting this error when I try to insert a new product:
"TypeError: Cannot read property 'sequence_value' of null"
This are the outputs of the above console logs.
first console log output:
function model (doc, fields, skipId) {
if (!(this instanceof model))
return new model(doc, fields, skipId);
Model.call(this, doc, fields, skipId);
}
second console log:
Null
can you see what I'm doing wrong?
You can run following line in your middleware:
console.log(products_counter.collection.collectionName);
that line will print products_counters while you expect that your code will hit products_counter. According to the docs:
Mongoose by default produces a collection name by passing the model name to the utils.toCollectionName method. This method pluralizes the name. Set this option if you need a different name for your collection.
So you should either rename collection products_counter to products_counters or explicitly configure collection name in your schema definition:
var Counter = new Schema({
_id: {type: String, required: true},
sequence_value: {type: Number, default: 0}
}, { collection: "products_counter" });

Where to place email validator within project?

I am on day 2 of learning node and Java Script. I have been following basic tutorials and decided to attempt and implement simple email validation into my code.
The problem is i am not sure where to place the code - i have a server.js file that holds all of the CRUD operations and a Mongoose model that which ensures the correct data is entered. Does anyone have any advice as to the best way to validate a user-entered email using this module?
//Email-validation npm module
var validator = require("email-validator");
validator.validate("test#email.com");
//Mongoose model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Tickets = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
address: {
type: String,
required: true
},
price: {
type: Number,
required: true,
min: 1,
max: 100
}
});
module.exports = mongoose.model('Ticket', TicketSchema);
Validate email before saving object. Code should look something like this:
Tickets.pre('save', function (next) {
var ticket = this;
if (!ticket.isModified('email')) {
next();
} else {
var valid = validator.validate(ticket.email);
if(valid) {
next();
} else {
next(valid);
}
}
});

mongoose user defined methods

I'm having trouble trying to add instance methods to my schemas.
Here is an example:
var mongoose = require('mongoose');
var bcrypt = require('bcryptjs');
var schema = new mongoose.Schema ({
first_name: {type: String, required: true, trim: true},
last_name: {type: String, required: true, trim: true},
email: {type: String, required: true, unique: true, dropDups: true, trim:true},
hash: {type: String, required: true}
});
schema.methods = {
encrypt: function(pwd) {
if (!pwd) return '';
else return bcrypt.hashSync(pwd, bcrypt.genSaltSync(10));
},
test: function(logentry) {
console.log(this.email + ': ' + logentry);
}
};
mongoose.model('Users', schema);
And then in my code elsewhere I try to call one of the methods:
var mongoose = require('mongoose');
var Users = mongoose.model('Users');
function testFunction(email) {
Users.find({email:email}, function(error, user) {
user.test('Trying to make mongoose instance methods work.');
});
}
testFunction('goofy#goober.com');
And then I get the following error (stacktrace omitted):
user.test('Trying to make mongoose instance methods work.');
^
TypeError: undefined is not a function
I cannot for the life of me figure this out..
I am using mongoose 3.8. I know I'm doing something wrong, but I need another, much smarter and experienced pair of eyes to help me find it.
I've tried defining the methods like this too:
schema.methods.encrypt = function(pwd) {...};
schema.methods.test = function(logentry) {...};
But it doesn't seem to matter.
There was only one previous post like this that I could find on stack overflow and they resolved their error by making sure that their methods were defined before they called mongoose.model('name', schema). I've got them defined before, so I don't think it's the same problem. Any help would be much appreciated.
The problem is that Users.find gives you an array.
So, either:
Users.find({ email: email }, function (e, users) {
users[0].test('foo bar whatever');
});
or:
Users.findOne({ email: email }, function (e, user) {
user.test('foo bar whatever');
});

Skip or Disable validation for mongoose model save() call

I'm looking to create a new Document that is saved to the MongoDB regardless of if it is valid. I just want to temporarily skip mongoose validation upon the model save call.
In my case of CSV import, some required fields are not included in the CSV file, especially the reference fields to the other document. Then, the mongoose validation required check is not passed for the following example:
var product = mongoose.model("Product", Schema({
name: {
type: String,
required: true
},
price: {
type: Number,
required: true,
default: 0
},
supplier: {
type: Schema.Types.ObjectId,
ref: "Supplier",
required: true,
default: {}
}
}));
var data = {
name: 'Test',
price: 99
}; // this may be array of documents either
product(data).save(function(err) {
if (err) throw err;
});
Is it possible to let Mongoose know to not execute validation in the save() call?
[Edit]
I alternatively tried Model.create(), but it invokes the validation process too.
This is supported since v4.4.2:
doc.save({ validateBeforeSave: false });
Though there may be a way to disable validation that I am not aware of one of your options is to use methods that do not use middleware (and hence no validation). One of these is insert which accesses the Mongo driver directly.
Product.collection.insert({
item: "ABC1",
details: {
model: "14Q3",
manufacturer: "XYZ Company"
},
}, function(err, doc) {
console.log(err);
console.log(doc);
});
You can have multiple models that use the same collection, so create a second model without the required field constraints for use with CSV import:
var rawProduct = mongoose.model("RawProduct", Schema({
name: String,
price: Number
}), 'products');
The third parameter to model provides an explicit collection name, allowing you to have this model also use the products collection.
I was able to ignore validation and preserve the middleware behavior by replacing the validate method:
schema.method('saveWithoutValidation', function(next) {
var defaultValidate = this.validate;
this.validate = function(next) {next();};
var self = this;
this.save(function(err, doc, numberAffected) {
self.validate = defaultValidate;
next(err, doc, numberAffected);
});
});
I've tested it only with mongoose 3.8.23
schema config validateBeforeSave=false
use validate methed
// define
var GiftSchema = new mongoose.Schema({
name: {type: String, required: true},
image: {type: String}
},{validateBeforeSave:false});
// use
var it new Gift({...});
it.validate(function(err){
if (err) next(err)
else it.save(function (err, model) {
...
});
})

Setting a virtual field in a Model based on an async query from another model

I want to have a user setting (in a user model) that is derived from the sum of values in another model.
What I have tried to do is create a virtual value using a query like this:
var schemaOptions = {
toObject: {
virtuals: true
}
,toJSON: {
virtuals: true
}
};
/**
* User Schema
*/
var UserSchema = new Schema({
firstname: String,
lastname: String,
email: String,
username: String,
provider: String,
phonenumber: Number,
country: String,
emailverificationcode: {type:String, default:'verifyme'},
phoneverificationcode: {type:Number, default:4321 },
emailverified: {type:Boolean, default:false},
phoneverified: {type:Boolean,default:false},
}, schemaOptions)
UserSchema
.virtual('credits')
.get(function(){
//Load Credits model
var Credit = mongoose.model('Credit');
Credit.aggregate([
{ $group: {
_id: '5274d0e5a84be03f42000002',
currentCredits: { $sum: '$amount'}
}}
], function (err, results) {
if (err) {
return 'N/A'
} else {
return results[0].currentCredits.toString();
//return '40';
}
}
);
})
Now, this gets the value but it fails to work correctly (I cannot retrieve the virtual 'value' credits). I think this is because of the async nature of the call.
Can someone suggest the correct way to achieve this?
Once again many thanks for any input you can provide.
Edit:
So I am trying to follow the suggested way but no luck so far. I cannot get my 'getCredits' method to call.
Here is what I have so far:
UserSchema.method.getCredits = function(cb) {
//Load Credits model
var Credit = mongoose.model('Credit');
Credit.aggregate([
{ $group: {
_id: '5274d0e5a84be03f42000002',
currentCredits: { $sum: '$amount'}
}}
], function (err, results) {
cb(results);
}
);
};
var User = mongoose.model('User');
User.findOne({ _id : req.user._id })
.exec(function (err, tempuser) {
tempuser.getCredits(function(result){
});
})
Any ideas? Thanks again
There are a few issues with your implementation:
UserSchema.method.getCredits
^^^^^^ should be 'methods'
Also, you have to make sure that you add methods (and virtuals/statics) to your schema before you create the model, otherwise they won't be attached to the model.
So this isn't going to work:
var MySchema = new mongoose.Schema(...);
var MyModel = mongoose.model('MyModel', MySchema);
MySchema.methods.myMethod = ... // too late, model already exists
Instead, use this:
var MySchema = new mongoose.Schema(...);
MySchema.methods.myMethod = ...
var MyModel = mongoose.model('MyModel', MySchema);
I would also advise you to always check/propagate errors.

Resources