@@ -634,7 +634,173 @@ DataAccessObject.upsert = function(data, options, cb) {
634634 }
635635 return cb . promise ;
636636} ;
637+ /**
638+ * Update or insert a model instance based on the search criteria.
639+ * If there is a single instance retrieved, update the retrieved model.
640+ * Creates a new model if no model instances were found.
641+ * Returns an error if multiple instances are found.
642+ * * @param {Object } [where] `where` filter, like
643+ * ```
644+ * { key: val, key2: {gt: 'val2'}, ...}
645+ * ```
646+ * <br/>see
647+ * [Where filter](https://docs.strongloop.com/display/LB/Where+filter#Wherefilter-Whereclauseforothermethods).
648+ * @param {Object } data The model instance data to insert.
649+ * @callback {Function } callback Callback function called with `cb(err, obj)` signature.
650+ * @param {Error } err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
651+ * @param {Object } model Updated model instance.
652+ */
653+ DataAccessObject . patchOrCreateWithWhere =
654+ DataAccessObject . upsertWithWhere = function ( where , data , options , cb ) {
655+ var connectionPromise = stillConnecting ( this . getDataSource ( ) , this , arguments ) ;
656+ if ( connectionPromise ) { return connectionPromise ; }
657+ if ( cb === undefined ) {
658+ if ( typeof options === 'function' ) {
659+ // upsertWithWhere(where, data, cb)
660+ cb = options ;
661+ options = { } ;
662+ }
663+ }
664+ cb = cb || utils . createPromiseCallback ( ) ;
665+ options = options || { } ;
666+ assert ( typeof where === 'object' , 'The where argument must be an object' ) ;
667+ assert ( typeof data === 'object' , 'The data argument must be an object' ) ;
668+ assert ( typeof options === 'object' , 'The options argument must be an object' ) ;
669+ assert ( typeof cb === 'function' , 'The cb argument must be a function' ) ;
670+ if ( Object . keys ( data ) . length === 0 ) {
671+ var err = new Error ( 'data object cannot be empty!' ) ;
672+ err . statusCode = 400 ;
673+ process . nextTick ( function ( ) { cb ( err ) ; } ) ;
674+ return cb . promise ;
675+ }
676+ var hookState = { } ;
677+ var self = this ;
678+ var Model = this ;
679+ var connector = Model . getConnector ( ) ;
680+ var modelName = Model . modelName ;
681+ var query = { where : where } ;
682+ var context = {
683+ Model : Model ,
684+ query : query ,
685+ hookState : hookState ,
686+ options : options ,
687+ } ;
688+ Model . notifyObserversOf ( 'access' , context , doUpsertWithWhere ) ;
689+ function doUpsertWithWhere ( err , ctx ) {
690+ if ( err ) return cb ( err ) ;
691+ ctx . data = data ;
692+ if ( connector . upsertWithWhere ) {
693+ var context = {
694+ Model : Model ,
695+ where : ctx . query . where ,
696+ data : ctx . data ,
697+ hookState : hookState ,
698+ options : options ,
699+ } ;
700+ Model . notifyObserversOf ( 'before save' , context , function ( err , ctx ) {
701+ if ( err ) return cb ( err ) ;
702+ data = ctx . data ;
703+ var update = data ;
704+ var inst = data ;
705+ if ( ! ( data instanceof Model ) ) {
706+ inst = new Model ( data , { applyDefaultValues : false } ) ;
707+ }
708+ update = inst . toObject ( false ) ;
709+ Model . applyScope ( query ) ;
710+ Model . applyProperties ( update , inst ) ;
711+ Model = Model . lookupModel ( update ) ;
712+ if ( options . validate === false ) {
713+ return callConnector ( ) ;
714+ }
715+ if ( options . validate === undefined && Model . settings . automaticValidation === false ) {
716+ return callConnector ( ) ;
717+ }
718+ inst . isValid ( function ( valid ) {
719+ if ( ! valid ) return cb ( new ValidationError ( inst ) , inst ) ;
720+ callConnector ( ) ;
721+ } , update , options ) ;
637722
723+ function callConnector ( ) {
724+ try {
725+ ctx . where = removeUndefined ( ctx . where ) ;
726+ ctx . where = Model . _coerce ( ctx . where ) ;
727+ update = removeUndefined ( update ) ;
728+ update = Model . _coerce ( update ) ;
729+ } catch ( err ) {
730+ return process . nextTick ( function ( ) {
731+ cb ( err ) ;
732+ } ) ;
733+ }
734+ context = {
735+ Model : Model ,
736+ where : ctx . where ,
737+ data : update ,
738+ currentInstance : inst ,
739+ hookState : ctx . hookState ,
740+ options : options ,
741+ } ;
742+ Model . notifyObserversOf ( 'persist' , context , function ( err ) {
743+ if ( err ) return done ( err ) ;
744+ connector . upsertWithWhere ( modelName , ctx . where , update , options , done ) ;
745+ } ) ;
746+ }
747+ function done ( err , data , info ) {
748+ if ( err ) return cb ( err ) ;
749+ var contxt = {
750+ Model : Model ,
751+ data : data ,
752+ isNewInstance : info && info . isNewInstance ,
753+ hookState : ctx . hookState ,
754+ options : options ,
755+ } ;
756+ Model . notifyObserversOf ( 'loaded' , contxt , function ( err ) {
757+ if ( err ) return cb ( err ) ;
758+ var obj ;
759+ if ( contxt . data && ! ( contxt . data instanceof Model ) ) {
760+ inst . _initProperties ( contxt . data , { persisted : true } ) ;
761+ obj = inst ;
762+ } else {
763+ obj = contxt . data ;
764+ }
765+ var context = {
766+ Model : Model ,
767+ instance : obj ,
768+ isNewInstance : info ? info . isNewInstance : undefined ,
769+ hookState : hookState ,
770+ options : options ,
771+ } ;
772+ Model . notifyObserversOf ( 'after save' , context , function ( err ) {
773+ cb ( err , obj ) ;
774+ } ) ;
775+ } ) ;
776+ }
777+ } ) ;
778+ } else {
779+ var opts = { notify : false } ;
780+ if ( ctx . options && ctx . options . transaction ) {
781+ opts . transaction = ctx . options . transaction ;
782+ }
783+ self . find ( { where : ctx . query . where } , opts , function ( err , instances ) {
784+ if ( err ) return cb ( err ) ;
785+ var modelsLength = instances . length ;
786+ if ( modelsLength === 0 ) {
787+ self . create ( data , options , cb ) ;
788+ } else if ( modelsLength === 1 ) {
789+ var modelInst = instances [ 0 ] ;
790+ modelInst . updateAttributes ( data , options , cb ) ;
791+ } else {
792+ process . nextTick ( function ( ) {
793+ var error = new Error ( 'There are multiple instances found.' +
794+ 'Upsert Operation will not be performed!' ) ;
795+ error . statusCode = 400 ;
796+ cb ( error ) ;
797+ } ) ;
798+ }
799+ } ) ;
800+ }
801+ }
802+ return cb . promise ;
803+ } ;
638804/**
639805 * Replace or insert a model instance: replace exiting record if one is found, such that parameter `data.id` matches `id` of model instance;
640806 * otherwise, insert a new record.
0 commit comments