Is there a way to define a virtual field in Geddy model? - node.js

Is it possible to define a temp field / virtual field in geddy model?
Like in the form I've use the input fields tmpFirstName and tmpLastName but when submitted I want to store the information in a single column name.
Thanks

This can be trivially achieved with the new lifecycle methods (thanks to you!).
In your controller:
this.create = function (req, resp, params) {
var self = this
, person = geddy.model.Person.create(params);
person.firstname = params.firstname;
person.lastname = params.lastname;
if (!person.isValid()) {
this.respondWith(person);
}
else {
person.save(function(err, data) {
if (err) {
throw err;
}
self.respondWith(person, {status: err});
});
}
};
In your model:
this.defineProperties({
name: {type: 'string'}
});
this.beforeSave = function () {
this.name = this.firstname + ' ' + this.lastname;
}
Note that you don't declare the "virtual" properties, otherwise geddy will store them in the database.

Related

Sequelize create belongTo instance with reference

I'm using Sequelize for my new NodeJs project
I defined two models: BusinessUnit and Store with this association: Store.belongsTo(BusinessUnit);
module.test_data_insertion = function() {
models.BusinessUnit.findOne({
where: {
BUID: "001"
}
}).then(element => {
fs.readdirSync(dir).forEach(function(file) {
var contents = fs.readFileSync(dir + file);
var jsonContent = JSON.parse(contents);
models.Store.create({
StoreId: jsonContent.StoreId,
StoreName: jsonContent.StoreName,
businessUnitId: element.id
});
});
});
};
I don't find to right way to reference the element in my Store, I would like something like this where I don't have to reference an id field directly
module.test_data_insertion = function() {
models.BusinessUnit.findOne({
where: {
BUID: "001"
}
}).then(element => {
fs.readdirSync(dir).forEach(function(file) {
var contents = fs.readFileSync(dir + file);
var jsonContent = JSON.parse(contents);
models.Store.create({
StoreId: jsonContent.StoreId,
StoreName: jsonContent.StoreName,
businessUnit: element
});
});
});
};
It should be simple but I don't see it. Thanks
Assuming your association are setup correctly, you are looking for something like this:
module.test_data_insertion = function() {
models.BusinessUnit.findOne({
where: {
BUID: "001"
}
}).then(element => {
fs.readdirSync(dir).forEach(function(file) {
var contents = fs.readFileSync(dir + file);
var jsonContent = JSON.parse(contents);
element.setStore({
StoreId: jsonContent.StoreId,
StoreName: jsonContent.StoreName,
});
});
});
};
please read belongsTo from doc which enables a set method,
However please note if this is a one to many relationship(one business unit with many stores) you may need to switch to belongsToMany it has add method. because setStore would override previous set.
Also, i'm not sure if any of those method would work inside .forEach correctly since they are promise, you may need to switch to a traditional for loop.

Mongoose encryption middleware doesn't call after aggregation

I have a mongoose's schema with a 'mongoose-encryption' plugin, for example:
let someSchema = new Schema({name, age});
someSchema.plugin(mongoose-encryption, {
encryptionKey: 'eKey',
signingKey: 'sKey',
encryptedFields: ['age'],
decryptPostSave: false
});
After initiating the model and the repository I tried to aggregate some query:
let aggregation = []; // just return all the docs.
someModel.aggregate(aggregation, (err, persons) => {
return persons;
});
As a result I'm still getting the age field encrypted, little reading has revealed that the 'post' method of the 'init' event isn't called after aggregation (as explained here - Mongoose Middleware Docs).
Is there a good solution? or any other workaround?
the data MUST be encrypt.
the aggregation is also required (in real life - lookup to other collection)
As I didn't find a better answer, I changed my code (as workaround unfortunately) to decrypt the object by myself -
using the code of mongoose-encryption to decrypt after the aggregation has finished.
Most of the code was taken from GitHub (called decryptOne in my code):
decryptSync function of mongoose-encryption
The 'tricky' thing was to decrypt the inner lookup value - the inner document also has the "_ct" field that should be decrypted.
let lookup: { [innerField: string]: string[]; } = {
user: ['bio']
};
this.decryptAggregation(aggregationResult, lookup);
My function gets a dictionary of the known lookup collection and its wanted fields after decryption. In that example, the other collection named users and its encrypted field is just his bio.
decryptAggregation(res: any[], innerLookup: { [innerField: string]: string[]; }) {
for (let doc of res) {
this.decryptSync(doc, innerLookup);
}
}
private decryptSync(doc: any, innerLookup: { [innerField: string]: string[]; }) {
this.decryptOne(doc, this.encryptedFields);
for (let innerObj in innerLookup) {
if (innerLookup.hasOwnProperty(innerObj)) {
this.decryptOne(doc[innerObj], innerLookup[innerObj]);
}
}
};
private decryptOne(doc: any, fields: string[]) {
let ct, ctWithIV, decipher, iv, idString, decryptedObject, decryptedObjectJSON, decipheredVal;
if (doc._ct) {
ctWithIV = doc._ct.hasOwnProperty('buffer') ? doc._ct.buffer : doc._ct;
iv = ctWithIV.slice(this.VERSION_LENGTH, this.VERSION_LENGTH + this.IV_LENGTH);
ct = ctWithIV.slice(this.VERSION_LENGTH + this.IV_LENGTH, ctWithIV.length);
decipher = crypto.createDecipheriv(this.ENCRYPTION_ALGORITHM, this.encryptionKey, iv);
try {
decryptedObjectJSON = decipher.update(ct, undefined, 'utf8') + decipher.final('utf8');
decryptedObject = JSON.parse(decryptedObjectJSON);
} catch (err) {
if (doc._id) {
idString = doc._id.toString();
} else {
idString = 'unknown';
}
throw new Error('Error parsing JSON during decrypt of ' + idString + ': ' + err);
}
fields.forEach((field) => {
decipheredVal = mpath.get(field, decryptedObject);
//JSON.parse returns {type: "Buffer", data: Buffer} for Buffers
//https://nodejs.org/api/buffer.html#buffer_buf_tojson
if (_.isObject(decipheredVal) && decipheredVal.type === "Buffer") {
this.setFieldValue(doc, field, decipheredVal.data);
} else {
this.setFieldValue(doc, field, decipheredVal);
}
});
doc._ct = undefined;
doc._ac = undefined;
}
}
After those function I got my wanted object fully decrypted, the last thing to do was to project the wanted fields back to the client - with lodash.pick

Meteor, MongoDb: Double inserting to collection on few occurences

I noticed this strange behaviour, when for few users only on production, it inserts every item multiple times to collection on asynchronous Meteor call. I tried multiple things, but nothing worked. I can't test on localhost, bc it never happens to me on localhost or in production.
I spent the whole night solving this, but didn't find any solution. I suppose it's caused by new Date(), but I have to call it somewhere. The production server is in Amsterdam and it seems like it happens only for users located further outside of Europe.
I found this to be similar issue, but can't really wrap my head on how to implement - https://github.com/meteor/meteor/issues/4263
Slug is what same songs are supposed to have the same.
This is the workflow, user clicks on song that triggers addNewSong function:
addNewSong = function (track) {
Globals.current_track = track;
checkIfSongAlreadySaved();
}
I need to check if song is already in collection, if it's -> route to it, else create the new song and route to it.
checkIfSongAlreadySaved = function() {
loadPrimaryGlobalItems();
Meteor.call('checkIfSongAlreadySaved', Globals.current_song_item_slug, function(error, result) {
if( result.item ) {
Globals.current_song_item_id = result.item._id;
Globals.current_song_item_slug = result.item.slug;
routeToSongPage();
if (! (result.item.download && result.item.mp3) ) {
downloadSong();
}
}
else {
loadSecondaryGlobalItems();
var item = {
slug:Globals.current_song_item_slug,
duration:Globals.current_duration,
thumbnail:Globals.current_song_thumbnail,
title:Globals.current_cleaned_song,
album:Globals.current_track.album,
artist:Globals.current_track.artists[0],
track:Globals.current_track.name,
date:result.date,
}
Globals.current_song_item_id = Songs.insert(item);
routeToSongPage();
downloadSong();
recentSongItem(result.date);
}
});
}
Add recent song
recentSongItem = function (date) {
Recentsongs.insert({
slug:Globals.current_song_item_slug,
songId:Globals.current_song_item_id,
title:Globals.current_cleaned_song,
duration:Globals.current_duration,
date:date,
});
}
If creating new song,
downloadSong = function() {
Meteor.call('findSong', Globals.current_song, function(error, result) {
console.log(result);
if (result) {
Globals.current_song_mp3 = true;
updateSongItemDownload(result.itemDetails);
}
else {
alert('not found')
}
});
}
and update song, to add download and mp3 values.
updateSongItemDownload = function(link) {
Songs.update({
_id: Globals.current_song_item_id
},
{
$set: {
download: link,
mp3: Globals.current_song_mp3,
}
});
}
Server methods:
Meteor.methods({
checkIfSongAlreadySaved: function(slug) {
return {item: Songs.findOne({slug:slug}), date: new Date()};
},
findSong:function(song) {
ServerGlobals.current_song = song;
var result = searchSite();
return result;
},
});
EDIT:
This is my subscription, just in case it might be causing the problem:
Template.songPage.onCreated(function() {
Session.set('processing', true);
var self = this;
self.autorun(function() {
var id = Router.current().params.id;
self.subscribe('singleSong', id);
var item = Songs.findOne({_id: id});
if (item) {
if (item.download) {
createSong(item.download);
}
else if( item.download === false ) {
console.log('item not found');
}
Session.set('loader', false);
Session.set('processing', false);
}
});
});
Meteor.publish('singleSong', function(id) {
check(id, String);
return Songs.find({_id: id});
});
You can apply a unique index on the slug field to ensure the same slug can only exist once and the second operation to insert will fail and show up as an error in your callback which you can discard or alert user as you desire.
db.collection.createIndex( { slug: 1 }, { unique: true } )
You will need to clear or modify the slug name on the dups from the db before applying the index though

Node.js: how to 'inherit' from abstract class?

I'm building a web app with node.js (+ angular, etc.).
The app will have to retrive some data (something like a catalog items list) from different providers, who expose their data in different ways.
In that module, I will have some function common to all providers, and some function unique to any of them.
My current (poor) implementation is something like this:
var providers = [
{ name: 'Apple', /* ... */ },
{ name: 'Samsung', /* ... */ },
{ name: 'Google', /* ... */ },
];
exports.syncCatalogues = function(search, callback) {
var allItems = [];
for (var p = 0; p < providers.length; p++) {
exports.getCatalog(providers[p], function(err, catalog) {
if (err) {
return callback(err);
}
exports.getItems(providers[p], catalog, function(err, items) {
if (err) {
return callback(err);
}
allItems = allItems.concat(items);
callback(null);
});
});
}
};
And my getCatalog() and getItems() implementation are as ugly as this:
exports.getCatalog(provider, callback) {
if (provider.name === 'Apple') {
// code for Apple provider ...
}
// and so on ...
};
exports.getItems(provider, callback) {
if (provider.name === 'Apple') {
// code for Apple catalog ...
}
// and so on ...
};
I know with ES5 (I'm stuck to it) abstract classes are not easy to implement, but I'm sure there is a better way (code more readable, maintainable, testable) than this... :-(
There are many ways to implement the inheritance in JavaScript.
Here is one, which I think the simplest, since you just operate with plain objects and use prototypal inheritance.
Instead of the base class you have a prototype object where you can place the common code.
Then you create an object based on the prototype and add specific code to it.
var providerPrototype = {
name: 'Prototype',
alertName: function() { // this is common function, all objects
alert(this.name); // will have it
}
};
var appleProvider = Object.create(providerPrototype);
appleProvider.name = 'Apple';
// this is a specific function for 'Apple'
appleProvider.getCatalog = function(callback) {
return callback(null, ['iPhone', 'Mac Mini']);
}
appleProvider.alertName = function() {
// call 'base' method
providerPrototype.alertName.call(this);
alert('All rights reserved.');
}
var samsungProvider = Object.create(providerPrototype);
samsungProvider.name = 'Samsung';
// this is a specific function for 'Samsung'
samsungProvider.getCatalog = function(callback) {
return callback(null, ['Galaxy S3', 'Galaxy S4']);
}
var providers = [
appleProvider, samsungProvider
];
var syncCatalogues = function(search, callback) {
var allItems = [];
for (var p = 0; p < providers.length; p++) {
var aProvider = providers[p];
aProvider.getCatalog(function(err, catalog) {
if (err) {
return callback(err);
}
aProvider.alertName(); // call the base method
alert(catalog);
});
}
};
syncCatalogues();
Check also Inheritance and the prototype chain in Mozilla javascript documentation.
And here is an example of splitting classes over node.js modules.
I'm new in nodeJS but I think my code below is possible only after ES6. I hope to help newbies like me. Follows:
class BaseClass {
constructor(){
console.log('Object created INHERITED');
}
toCallFromChild(){
console.log('Called by child');
this.toOverride();
}
toOverride(){} //to override
}
class childClass extends BaseClass{
toOverride(){
console.log ('Override by child');
}
}
var instance = new childClass();
instance.toCallFromChild();
My response is a possible alternative solution.
I personnally do not like the native javascript object mecanics. So i generaly use a library like Mootools for making clean objects.
Example from Mootools documentation :
var Animal = new Class({
initialize: function(age){
this.age = age;
}
});
var Cat = new Class({
Extends: Animal,
initialize: function(name, age){
this.parent(age); // calls initalize method of Animal class
this.name = name;
}
});
var myCat = new Cat('Micia', 20);
alert(myCat.name); // alerts 'Micia'.
alert(myCat.age); // alerts 20.
Se the online doc on : http://mootools.net/

why update doesn't work

I pass a json variable to a module but I can't do the update of my collection, always I have an error in the updating.
var gestion = function(myJSON) {
var dburl = 'localhost/mongoapp';
var collection = ['clientes'];
var db = require('mongojs').connect(dburl, collection );
function cliente(nombre, estado, nuevo){
this.nombre = nombre;
this.estado = estado;
this.nuevo = nuevo;
}
var cliente1 = new cliente(myJSON.nombre myJSON.estado, myJSON.nuevo);
if (cliente1.estado == "desconectado"){
db.clientes.update(cliente1.nombre, {$set: {estado: "desconectado", nuevo: "no"}}, function(err) {
if (err) console.log("error "+cliente1.nombre);
else console.log("OK");
});
}
}
return 0;
}
I also tried to remove my db and create one more time and I'm sure that my object exist in my db.
The signature you should be using is
update(query, update, callback)
but you're passing a string for query, which doesn't mean anything to Mongo. You may want to look at the docs for an overview, but for this specific instance, it looks like you're trying to find the document where nombre is equal to the string at cliente1.nombre. The query for this is a dictionary { nombre: cliente1.nombre }, so that line should be
db.clientes.update({nombre: cliente1.nombre}, {$set: {estado: "desconectado", nuevo: "no"}}, function(err) {

Resources