Mongoose Setters only get called when create a new doc? - javascript

I use Mongoose getter and setter I recently found that setter only applies when I create a new doc (UserSchema.create...) but setter will not get called for update (UserSchema.findByIdAndUpdate...). Am I right or am I missing anything. If I'm right, is there setter for update?
(http://mongoosejs.com/docs/2.7.x/docs/getters-setters.html
Thanks.
UPDATED
I found git issue: https://github.com/LearnBoost/mongoose/issues/751 , it means this is expected behavior. Not sure I'm right.

Found out that I should not use findByIdAndUpdate, instead I should query object, edit and save.

Found a nice method that encapsulates that from https://groups.google.com/forum/?fromgroups=#!topic/mongoose-orm/8AV6aoJzdiQ:
function findByIdAndSave( model, id, data, next ){
model.findById( id, function( err, doc ) {
if( err ){
next( err, null );
} else {
if(! doc ){
next( new Error("Object to save not found"), null );
} else {
// There must be a better way of doing this
for( var k in data ){
doc[k] = data[k];
}
doc.save( next );
}
}
});
}
(This really belongs as a comment, but the answer allows better code formatting)

Related

Detecting unexpected properties

Often I use objects to pass around a set of options.
In 99.9% of these cases I expect these objects to only contain a subset of the property I use. If an unexpected property is present, it means almost always that there's a typo or a logical error.
Is there a simple way to make sure that the option objects don't contain unexpected properties? I would only really need this during testing and debugging, so it doesn't have to be efficient.
Example:
function log( text, opts={} ) {
const {times=1, silent=false, logger=console} = opts
if (silent) return
for (let i=0; i<times; i++) {
logger.log( text )
}
}
// all good here
log( "Hello World!", {times:1} )
// "slient" is not a valid option. It's a typo.
// I want this call to throw an exception.
log( "Bye World!", {times:2, slient:true} )
I know that I can implement it through a function that receives the names of the expected properties:
function testOpts( opts={}, optNames=[] ) { /* ... */ }
testOpts( opts, ["times", "silent", "logger"] )
But maintaining this is boring and error prone: every time I change an option I need to update those parameters. I tried it and too often I forget to update it, so my function keeps accepting parameters that I removed, and this is something I want to avoid.
Is there a better solution that lets me write the property names only once?
If you only want to write them once, the only way to do that is in the log function where you are using them. There is indeed a way to do that: use rest syntax in the destructuring, and test that there are no other properties than the expected (destructured) ones:
function log( text, opts={} ) {
const {times=1, silent=false, logger=console, ...rest} = opts
// ^^^^^^^
assertEmpty(rest);
if (silent) return
for (let i=0; i<times; i++) {
logger.log( text )
}
}
function assertEmpty(val) {
const keys = Object.keys(val);
if (keys.length) {
throw new RangeError(`Unexpected properties ${keys.join(', ')} in object`);
}
}
Other than that, use a static analysis tool such as TypeScript or Flow, they will easily catch these mistakes.

Wierd AngularJS error

I am rather new to AngularJS programming and was working on a sample application. I came across a problem while returning an object from a service. Consider following code in my custom service:
this.getCompanyInfo = function(companyID)
{
console.log( companyID );
angular.forEach( companyInfo, function( coInf ) {
if( coInf.companyID == companyID )
{
console.log(coInf);
return coInf;
}
})
}
in this code, companyInfo is an array containing information about companies, each company is represented by an object. the second console.log is showing this:
Object {companyID: "CHCL", companyName: "Chilime Hydropower", stockPriceTrend: Array[4]}
in my controller, I have this:
$scope.companyInfo = dataServices.getCompanyInfo( $routeParams.companyID);
console.log($scope.companyInfo);
but here, console.log says 'undefined'.
I don't know what wrong I have been doing, any help will be highly appreciated!
Regards.
You are just returning from the iterator function argument of the forEach (not from getCompanyInfo) which will still go on. But you need to return the actual value from your getCompanyInfo function. You can just use a simple for loop and return the value once you find a match.
this.getCompanyInfo = function(companyID)
{
var cInfo, i, l;
for(i=0,l=companyInfo.length; i<l;i++){
if((cInfo = companyInfo[i]).companyID === companyID ){
return cInfo ;
}
}
}
Returning from the iterator function does not break the looping using angular.forEach. Loop will still go on.
change function to
this.getCompanyInfo = function(companyID)
{
var coInfo;
console.log( companyID );
angular.forEach( companyInfo, function( coInf ) {
if( coInf.companyID == companyID )
{
console.log(coInf);
coInfo = coInf;
}
})
return coInfo;
}
you are not returning value from function but from forEach and that will not return the value from the function.
it has nothing to do with angular, btw.
EDIT
also you can use libraries like lodash/underscore for search/filter functionalities

Passing parameters to a jQuery closure not working on Multisuggest plugin

I have a question of which someone might find this much simpler than I do, but alas, I don't have much experience with custom jQuery plugins.
The previous developer at my place of work left me with a lot of left-over plugins that don't seem to work very well, most which I've been able to fix but this which has been bugging me for a while.
It is a custom Multiple Suggestion plugin (called multisuggest) written in jQuery, and it has a set of functions that it uses internally (*e.g. setValue to set the value of the box, or lookup to update the search)*
It seems he's tried to call these plugin functions from an external script (this exteranl script specifically imports newly created suggestions into the multisuggest via user input and sets the value) like this:
this.$input.multisuggest('setValue', data.address.id, address);
This seems to call the function as it should, except the second and third parameters don't seem to be passed to the function (setValue receives nothing), and I don't understand how I can get it to pass these. It says it is undefined when I log it in the console. The functions are set out like this (I've only including the one I'm using and an internal function from multisuggest called select that actually works):
MultiSuggest.prototype = $.extend(MultiSuggest, _superproto, {
constructor : MultiSuggest,
select: function () { // When selecting an address from the suggestions
var active, display, val;
active = this.$menu.find('.active');
display = active.attr('data-display');
val = active.attr('data-value');
this.setValue(display, val, false); // This works, however when I do it as shown in the above example from an external script, it doesn't. This is because it doesn't receive the arguments.
},
setValue : function(display, value, newAddress) { // Setting the textbox value
console.log(display); // This returns undefined
console.log(value); // This returns undefined
if (display && display !== "" &&
value && value !== "") {
this.$element.val(this.updater(display)).change();
this.$hiddenInput.val(value);
this.$element.addClass("msuggest-selected");
}
if(newAddress === false){
return this.hide();
}
},
});
Why does it listen to the function, but not the values passed to it? Do I need to include an extra line of code somewhere to define these arguments?
Anyone with jQuery experience would be of great help! This is bottlenecking progress on a current project. Thanks for your time!
EDIT:
I've missed out the code of how the arguments are trying to be passed from the external script to the internal function of the plugin. Here is the plugin definition with how the external call is handled, can anyone see a problem with this?
$.fn.multisuggest = function(option) {
return this.each(function() {
var $this = $(this), data = $this.data('multisuggest'), options = typeof option === 'object' && option;
if (!data) {
$this.data('multisuggest', ( data = new MultiSuggest(this, options)));
} else if (typeof(option) === 'string') {
var method = data[option];
var parameters = Array.prototype.slice.call(arguments, 1);
method.apply(this, parameters);
}
});
};
The "usual" plugin supervisor looks like this :
// *****************************
// ***** Start: Supervisor *****
$.fn.multisuggest = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || !method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist in jQuery.' + pluginName );
}
};
// ***** Fin: Supervisor *****
// ***************************
All the looping through this should be inside the method functions, not in the supervisor.
I'm a little worried that new MultiSuggest(...) appears in the current supervisor. That sort of thing is totally unconventional. The original author clearly had something in mind.
You need to extend the jQuery plugin function which is attached to $.fn['multisuggest'], that function is probably only taking and passing one parameter.

jQuery / javascript argument handling question

First of all I don't know how to phrase the question "title", sorry if I am confusing everyone with the title here.
Anyway, I saw this code at jQuery http://docs.jquery.com/Plugins/Authoring
(function( $ ){
var methods = {
init : function( options ) {
return this.each(function(){
var $this = $(this),
data = $this.data('tooltip'),
tooltip = $('<div />', {
text : $this.attr('title')
});
// If the plugin hasn't been initialized yet
if ( ! data ) {
/*
Do more setup stuff here
*/
$(this).data('tooltip', {
target : $this,
tooltip : tooltip
});
}
});
},
destroy : function( ) {
return this.each(function(){
var $this = $(this),
data = $this.data('tooltip');
// Namespacing FTW
$(window).unbind('.tooltip');
data.tooltip.remove();
$this.removeData('tooltip');
})
},
reposition : function( ) { // ... },
show : function( ) { // ... },
hide : function( ) { // ... },
update : function( content ) { // ...}
};
$.fn.tooltip = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
My question being is that I cannot understand why do we need this if statement?
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
}
Or in other words, in what scenario that we will pass in argument like "methods[method]" base on the example?
Thanks!
That if statement will check if you are trying to call one of the methods available to the plugin. In the case of you example you have these methods:
init, destroy, reposition, show, hide, and update
So you can do a call like :
$.tooltip('init', { arg1: true, arg2: 'a value' });
Then your code knows where to send the arguments because this if statement will be true:
if(methods['init'])
You see at the beginning that the code defines an object methods.
The function $.fn.tooltip = function( method ) accepts an argument with name method (no s at the end).
The function will execute one of the methods defined in methods, but it can only do it, if this method is also available. Hence the if(methods[method]).
The expression will be true if method is e.g. show, hide, update, etc, i.e. if the methods object has a property with the name contained in method.
Therefore the expression will be false for foo or bar. If the if statement would not be there, the code would try to call method['foo'], which does not exist and you would get an error:
TypeError: object is not a function
Is this what you wanted to know?
Your code snippet isn't complete and it doesn't contain a demo to show how it's called, so it's hard to give a definite answer.
However, here's what I think from what the code looks like:
The if statement is necessary because the tooltip function will be called with arguments such as init, destroy, show, hide, update, which refer to the functions defined in the methods hash. You probably call tooltip with init to initialize the tooltip, hide to hide it, show to show it etc. If you don't pass an argument at all, it defaults to the init method and initializes the tooltip (second branch of the if).
First of all, the piece of code declares an hashmap named methods which contains some functions.
Then, the second part declares a function named tooltip which takes a parameter named method. This parameter is the name of the function we want to call, this name is the index of this function in the methods array.
So, when you do $('#whatever').tooltip('destroy'); it will look in the methods array for the function referenced with the destroy key.

Observer Pattern question - JavaScript Object delete itself from a parent array?

I'm trying to write an object relationship that follows an observer pattern where the observer cares about a specific set of events that occur on the subject.
I'm not sure if this is 100% standard, but the way I've constructed it, these event objects are defined within the observer, with custom callbacks that will fire when the event occurs. When an event occurs on the subject, it goes through all it's observers and looks to see who is watching for this event. If it finds observers watching for this event, it triggers the observer event's callback.
Since I want my observers to have the flexibility to add and remove, on the fly, events that care to watch, I need for the event object to have the ability to delete itself after running it's callback... say, for instance, if my observer only wants to respond to an event once, then no longer monitor it.
This seems like a good plan, but I know that a JavaScript object can't have call delete() on itself.
I was just curious is anyone else out there had ran into this and had come up with an effective solution.
My only thought was that I could pass the reference to the parent observer object, to it's child event, then when the callback happens, I could call a method within the parent... something like removeEvent(this) passing the event itself to this function. The removeEvent function could then splice out the event from it's array of events. The only complicated problem would be finding the location of this event object in the array. (taking suggestions on this too, thanks!).
Thanks in advance for the help!
If you pass in a reference to the event object in your removeEvent method, then you can just loop through all of the events and test with the == operator -- since the event object you passed in is a reference, it will evaluate to true when compared to the corresponding event object in the loop because they reference the same object in memory.
I thought I'd have a go at answering your question but I'm not 100% sure how you've implemented your observer pattern. Perhaps there's something useful in my snippet below. I've assumed that the observation is done via callback functions and I've assumed nothing about the format of that data so I'm using strings.
The crux of my solution is that the callback receives a reference to the Subject (which is usual in the observer pattern). This reference can be used to detach the callback from the Subject.
var Subject = {
observers: {},
attach: function( eventType, fn ) {
if( !this.observers[eventType] ) this.observers[eventType] = [];
this.observers[eventType].push( fn );
},
detach: function( fn ) {
var newObservers,
eventType,
i;
for( eventType in this.observers ) {
newObservers = [];
for( i = 0; i < this.observers[eventType].length; i++ ) {
if( this.observers[eventType][i] !== fn ) newObservers.push( this.observers[eventType][i] );
}
this.observers[eventType] = newObservers;
}
},
notify: function( eventType, data ) {
var i, observers = this.observers[eventType].slice(0);
for( i = 0; i < observers.length; i++ ) {
observers[i]( data, this );
}
},
poke: function() {
this.notify( 'testing', 'I got poked' );
}
};
var Observer = {
logEvent: function( data, subject ) {
console.log( 'Every time: ' + data );
},
logEventOnce: function( data, subject ) {
console.log( 'Just once: ' + data );
/*
* THE CRUX
*/
subject.detach( arguments.callee );
}
};
Subject.attach( 'testing', Observer.logEvent );
Subject.attach( 'testing', Observer.logEventOnce );
Subject.poke();
//Every time: I got poked
//Just once: I got poked
Subject.poke();
//Every time: I got poked
Subject.poke();
//Every time: I got poked
Subject.poke();
//Every time: I got poked

Categories

Resources