Using aspect.around, but checking for methods calling each other

520 Views Asked by At

I would like to run some specific code around put() and add() for Dojo stores. The problem I am having is that for JSON REST stores, in JsonRest.js add() is just a function that calls put():

add: function(object, options){
  options = options || {};
  options.overwrite = false;
  return this.put(object, options);
},

So, if I use aspect.around() with add(), my code ends up being executed twice IF I apply my code to stores created with a store that implements add() as a stub to put().

Please note that I realise that most stores will do that. I just want my solution to be guaranteed to work with any store, whether there is method nesting or not.

Dojo's own Observable.js has the same problem. This is how they deal with it:

function whenFinished(method, action){
    var original = store[method];
    if(original){
      store[method] = function(value){
        if(inMethod){
          // if one method calls another (like add() calling put()) we don't want two events
          return original.apply(this, arguments);
        }
        inMethod = true;
        try{
          var results = original.apply(this, arguments);
          Deferred.when(results, function(results){
            action((typeof results == "object" && results) || value);
          });
          return results;
        }finally{
          inMethod = false;
        }
      };
    }
  }
  // monitor for updates by listening to these methods
  whenFinished("put", function(object){
    store.notify(object, store.getIdentity(object));
  });


  whenFinished("add", function(object){
    store.notify(object);
  });
  whenFinished("remove", function(id){
    store.notify(undefined, id);
  });

My question is: is there a simple, "short" way to change my existing code so that it checks if it's within a method, and avoid running the code twice?

I gave it a go, but I ended up with clanky, hacky code. I am sure I am missing something...

Here is my existing code:

topic.subscribe( 'hotplate/hotDojoStores/newStore', function( storeName, store ){

  aspect.around( store, 'put', function( put ){

    return function( object, options ){

      return when( put.call( store, object, options ) ).then( function( r ) {
        var eventName;
        var identity = store.idProperty;
        eventName = object[ identity ] ? 'storeRecordUpdate' : 'storeRecordCreate';

        topic.publish( eventName, null, { type: eventName, storeName: storeName, objectId: r[ identity ], object: object }, false );

      } );

    }
  });

  aspect.around( store, 'add', function( add ){
    return function( object, options ){

      return when( add.call( store, object, options ) ).then( function( r ) {

        var identity = store.idProperty;

        topic.publish('storeRecordCreate', null, { storeName: storeName, storeTarget: storeTarget, objectId: r[identity], object: object }, false }  );

      });
    }
  });
});
1

There are 1 best solutions below

4
On BEST ANSWER

This is my attempt... What I don't really "get" about my attempt is whether it's 100% safe or not.

If store.add() is called twice in a row, is is ever possible that inMethod is set to true by the first call, and that the second add() call then finds it already set to true because the first one hasn't managed to set it to false yet?

This would only really be possible if nextTick() is called in between the two calls I assume?

Or am I just completely confused by it all? (Which is very possible...)

  topic.subscribe( 'hotplate/hotDojoStores/newStore', function( storeName, store ){

    var inMethod;

    aspect.around( store, 'put', function( put ){

      return function( object, options ){

        if( inMethod ){
          return when( put.call( store, object, options ) );
        } else {

          inMethod = true;

          try {
            return when( put.call( store, object, options ) ).then( function( r ) {
              var eventName;
              var identity = store.idProperty;
              eventName = object[ identity ] ? 'storeRecordUpdate' : 'storeRecordCreate';

              topic.publish( eventName, null, { type: eventName, storeName: storeName, objectId: r[ identity ], object: object }, false );

            });
          } finally {
            inMethod = false;
          }

        }

      }
    });

    aspect.around( store, 'add', function( add ){
      return function( object, options ){

        if( inMethod ){
          return when( add.call( store, object, options ) );
        } else {

          inMethod = true;

          try {

            return when( add.call( store, object, options ) ).then( function( r ) {

              var identity = store.idProperty;

              topic.publish('storeRecordCreate', null, { type: 'storeRecordCreate', storeName: storeName, objectId: r[identity], object: object }, false );

            });
          } finally {
            inMethod = false;
          }
        }
      }

    });

    aspect.around( store, 'remove', function( remove ){
      return function( objectId, options ){

        return when( remove.call( store, objectId, options ) ).then( function( r ) {

          topic.publish('storeRecordRemove', null, { type: 'storeRecordRemove', storeName: storeName, objectId: objectId }, false );

        });
      };
    });

  });