Decent Exposure gem, Mongoid and find_by method

Decent Exposure is ‘a Ruby gem for cleaner controllers’ and I love it.
As for now, hovewer, it lacks a strategy for use with Mongoid. In most basic cases a strategy for ActiveRecord works fine, but it breaks when one needs to use a custom finder. That is because Mongoid doesn’t provide dynamic finders – instead of ActiveRecord style:

Article.find_by_name('foo')

(which will be deprecated in Rails 4 AFAIK) we use:

Article.find_by(name: 'foo')

I wrote a simple initializer which monkey-patches decent_exposure allowing us to use lambda as a ‘finder’ option, like this:

expose :article, finder: lambda { |c| find_by(name: c.params[:id]) }

Just drop these lines of code into config/initializers/decent_exposure_mongoid.rb

module DecentExposure
  class ActiveRecordStrategy < Strategy
    def singular_resource
      if id
        case finder
          when Symbol
            scope.send(finder, id)
          when Proc
            scope.instance_exec(controller, &finder)
          else
            raise "Symbol or Proc needed"
        end

      else
        scope.new
      end
    end
  end
end

ActiveRecord dynamic attributes as serializable Struct

ActiveRecord gives us a nice feature to store dynamic attributes in a single database column:

class Product < ActiveRecord::Base
  serialize :props, Hash
end

If you use it often it may be a sign that you should think about a different storage than a database.
But sometimes it is convenient to denormalize your data.

If you don’t know your attributes until runtime, it is the way to go.
But sometimes you know them, but still want to denormalize for some reasons.

Asynchronous singleton API

After an awesome tech talk with @petermichaux, @racino, @varjs, @qvist, we came to an interesting point, as how to handle a certain situation:

  • you need a singleton
  • you need it to instantiate lazily
  • its instantiation involves querying server-side, thus you only want to do it when it’s absolutely necessary (hence the lazy approach)
  • obviously, many parts of the system may be wanting to use what that singleton offers, in any given order, at any given time

What problems does this pose? It is somewhat similar to what is encountered in Java, where threads are involved. You would probably want the getInstance method to be synchronized. This is to some extent also true in case of the lazy, asynchronous instantiation in JavaScript. Because what happens, when getInstance is called multiple times, before instantiation is finished? Bad, unpredictable things.

This could seem as an obvious call for the deferred approach. But what got cooked rather resembled the state machine. So, enough talking, let’s code!

Passing the singleton object instance

We cannot get the singleton and assign it immediately, like so:

var mySingletonInstance = MySingleton.getInstance();

since it might not return anything at that point. We need to do it using callbacks passed to the instantiation logic, which get called when the job gets done. So we would rather go like this when using the singleton:

MySingleton.getInstance({
    success : function(singletonInstance) {
        // here we get access to our desired instance
    },
    failure : function(error) {
        // bugger, something went wrong
    }
});

Concurrent singleton instance access

All cool, but we still need to handle the case where multiple other parts of the system request access to the instance before it gets created. Like this:

// in module foo
MySingleton.getInstance({
    success : function(singletonInstance) {
        // do something useful with what the server provided
    },
    failure : function(error) {
        // bugger, something went wrong
    }
});

// in module bar
MySingleton.getInstance({
    success : function(singletonInstance) {
        // do something just as useful
    },
    failure : function(error) {
        // bugger, something went wrong
    }
});

Callback queue

What we need be doing, is collect all the callbacks, and execute them once the object is ready to be used. So let’s build up a skeleton of the solution:

var MySingleton = {};
(function() {
    var callbackQueue = [];
    var instance = null;
    var doRequests = function(callbacks) {
        //code for interaction with the server
    };
    MySingleton.getInstance = function(callbacks) {
    }
}());

States

Let’s think about what state our singleton can be in. One is before instantiation, which you could call before. When it’s called for its instance, it could be working. Once all is done it could be success, and if something went wrong, failure. Extending our skeleton with the states:

var MySingleton = {};
(function() {
    var state = "before";
    var callbackQueue = [];
    var instance = null;
    var doRequests = function(callbacks) {
        //code for interaction with the server
    };
    MySingleton.getInstance = function(callbacks) {
        switch (state) {
            case "before":
                //this would be the case of the first call for the instance; here we start instantiating and queue up the first callback
                state = "working";
                callbackQueue.push(callbacks);
                break;
            case "working":
            //here we just queue up further callbacks
            case "success":
            //in this case we execute the success callback immediately
            case "failure":
            //like in success, but we pass some custom error object to the failure callback
            default:
                throw new Error("Invalid state: " + state)
        }
    };
}());

Asynchronous instantiation

One extra thing we’ve got to remember is that the instantiation itself, due to its asynchronous nature, also happens with the help of callbacks, hence the extra handling of them in the before branch:

var MySingleton = {};
(function() {
    var state = "before";
    var callbackQueue = [];
    var myInstance = null;
    var error = null;
    var doRequests = function(callbacks) {
        //code for interaction with the server
    };
    MySingleton.getInstance = function(callbacks) {
        switch (state) {
            case "before":
                state = "working";
                callbackQueue.push(callbacks);
                doRequests({
                    success : function(singletonInstance) {
                        state = "success";
                        myInstance = singletonInstance;
                    },
                    failure : function(errorObj) {
                        state = "failure";
                        error = errorObj;
                    }
                });
                break;
            case "working":
            //here we just queue up further callbacks
            case "success":
            //in this case we execute the callback immediately
            case "failure":
            //like in success, but we pass some custom error object
            default:
                throw new Error("Invalid state: " + state)
        }
    };
}());

Resolving callback queue

Now let’s take care of resolving the callback queue. Throughout this article, I’ve been assuming the callbacks were passed in form of an object like:

MySingleton.getInstance({
    success : function(singletonInstance) {
    },
    failure : function(error) {
    }
});

So here goes:

var MySingleton = {};
(function() {
    var state = "before";
    var callbackQueue = [];
    var myInstance = null;
    var error = null;
    var doRequests = function(callbacks) {
        //code for interaction with the server
    };
    MySingleton.getInstance = function(callbacks) {
        switch (state) {
            case "before":
                state = "working";
                callbackQueue.push(callbacks);
                doRequests({
                    success : function(singletonInstance) {
                        state = "success";
                        myInstance = singletonInstance;
                        for (var i = 0, ilen = callbackQueue.length; i < ilen; i++) {
                            callbackQueue[i].success(myInstance);
                        }
                        callbackQueue = [];
                    },
                    failure : function(errorObj) {
                        state = "failure";
                        error = errorObj;
                        for (var i = 0, ilen = callbackQueue.length; i < ilen; i++) {
                            callbackQueue[i].failure(error);
                        }
                        callbackQueue = [];
                    }
                });
                break;
            case "working":
            //here we just queue up further callbacks
            case "success":
            //in this case we execute the callback immediately
            case "failure":
            //like in success, but we pass some custom error object
            default:
                throw new Error("Invalid state: " + state)
        }
    };
}());

Final solution

Let’s build the rest of the state branches:

var MySingleton = {};
(function() {
    var state = "before";
    var callbackQueue = [];
    var myInstance = null;
    var error = null;
    var doRequests = function(callbacks) {
        //code for interaction with the server
    };
    MySingleton.getInstance = function(callbacks) {
        switch (state) {
            case "before":
                state = "working";
                callbackQueue.push(callbacks);
                doRequests({
                    success : function(singletonInstance) {
                        state = "success";
                        myInstance = singletonInstance;
                        for (var i = 0, ilen = callbackQueue.length; i < ilen; i++) {
                            callbackQueue[i].success(myInstance);
                        }
                        callbackQueue = [];
                    },
                    failure : function(errorObj) {
                        state = "failure";
                        error = errorObj;
                        for (var i = 0, ilen = callbackQueue.length; i < ilen; i++) {
                            callbackQueue[i].failure(error);
                        }
                        callbackQueue = [];
                    }
                });
                break;
            case "working":
                callbackQueue.push(callbacks);
                break;
            case "success":
                callbacks.success(myInstance);
                break;
            case "failure":
                callbacks.failure(error);
                break;
            default:
                throw new Error("Invalid state: " + state)
        }
    };
}());

We’re done!

And that’s about it! Further improvements may include making MySingleton and its getInstance protected against overwriting. To achieve this, we could use ES5 object and property descriptors, some of which I’ve already covered.