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