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.
ES5 in My App: impregnable namespaces, getters and setters
NOTE: this will only work within a JavaScript runtime supporting ECMAScript 5.
To check, what the situation looks like across browsers, check out Kangax’s ES5 compatibility table
Using ES5 property descriptors, or rather relying upon their default values, we can attach our application code to the global namespace and prevent it from being overwritten.
Consider this piece of code:
(function(global) {
Object.defineProperty(global, "MyApp", {
value: {}
});
}(this));
Considering the defaults, we get ourselves an object under the global called “MyApp”, which is guaranteed to be accessible under that very name. Always.
According to the specs, if you later do something like:
(function(global) {
global["MyApp"] = "MyNewApp";
}(this));
then global["MyApp"] still points to the same object, albeit no error is thrown, which makes me a little sad. Anyway, that shouldn’t be a collision very hard to debug.
So, in effect, we got as a globally accessible MyApp object, ready to be filled with parts of our app.
If you have anything that acts sort of singleton-like (eg. an overlay mechanism), you could add that to MyApp, and also prevent it from being overwritten:
(function() {
// initialization code
var setContent = function(content) {
// ...
};
var getContent = function() {
// ...
};
var show = function(content) {
// ...
};
var hide = function() {
// ...
};
Object.defineProperty(MyApp, "overlay", {
value: {
show: show,
hide: hide,
setContent: setContent
}
});
}());
The defaults again make the property “overlay” of the MyApp object point to the same value, even if you try to overwrite it.
Obviously, if there is too much being set to “read only”, you will probably end up not being able to use other programming techniques, like maybe inheriting, mixing in, or other. Protect your code only as much as necessary, but not less ;)
Another interesting thing for how to utilize Object goodness of ES5, is setters and getters. Imagine we have a news ticker on our site, which could also be singletonish.
We set it up:
(function() {
// initialization code
var rootEl = document.createElement("div");
// attaching ticker rootEl wherever it belongs
var fade = function(dir){
// some visual effects we may want to come in play when setting
}
var setContent = function(content) {
fade("out");
rootEl.innerHTML = content;
fade("in");
};
var getContent = function() {
return rootEl.innerHTML;
};
Object.defineProperty(MyApp, "ticker", {
set: setContent,
get: getContent
});
}());
To change the content of a DOM node, we just set the value of our MyApp.ticker, eg.:
MyApp.ticker = "Some grand breaking news yay!";
Pretty simple, and definitely aiding in code stability and encapsulation.
