Permalink
Browse files

Added `selectJSON` method to entity objects, see README.md for

documentation.
  • Loading branch information...
1 parent 08ab596 commit d714e0d52bfa5a13ca33e978b75a797838fe3061 @zefhemel zefhemel committed Sep 22, 2010
Showing with 209 additions and 1 deletion.
  1. +29 −0 README.md
  2. +180 −1 lib/persistence.js
View
@@ -347,6 +347,35 @@ And of course the methods to define relationships to other entities:
* `EntityName.hasOne(property, Entity)` defines a 1:1 or N:1
relationship
+
+Entity objects
+--------------
+
+Entity instances also have a few predefined properties and methods you
+should be aware of:
+
+* `obj.id`, contains the identifier of your entity, this is a
+ automatically generated (approximation of a) UUID. You should
+ never write to this property.
+* `obj.fetch(prop, callback)`, if an object has a `hasOne`
+ relationship to another which has not yet been fetched from the
+ database (e.g. when `prefetch` wasn't used), you can fetch in manually
+ using `fetch`. When the property object is retrieved the callback function
+ is invoked with the result, the result is also cached in the entity
+ object itself.
+* `obj.selectJSON([tx], propertySpec, callback)`, sometime you need to extract
+ a subset of data from an entity. You for instance need to post a JSON representation of your entity, but do not want to include all properties. `selectJSON` allows you to do that. The `propertySpec` arguments expects an array with property names. Some examples:
+ * `['id', 'name']`, will return an object with the id and name property of this entity
+ * `['*']`, will return an object with all the properties of this entity, not recursive
+ * `['project.name']`, will return an object with a project property which has a name
+ property containing the project name (hasOne relationship)
+ * `['project.[id, name]']`, will return an object with a project property which has an
+ id and name property containing the project name
+ (hasOne relationship)
+ * `['tags.name']`, will return an object with an array `tags` property containing
+ objects each with a single property: name
+
+
Query collections
-----------------
View
@@ -414,6 +414,181 @@ persistence.get = function(arg1, arg2) {
return json;
};
+
+ /**
+ * Select a subset of data as a JSON structure (Javascript object)
+ *
+ * A property specification is passed that selects the
+ * properties to be part of the resulting JSON object. Examples:
+ * ['id', 'name'] -> Will return an object with the id and name property of this entity
+ * ['*'] -> Will return an object with all the properties of this entity, not recursive
+ * ['project.name'] -> will return an object with a project property which has a name
+ * property containing the project name (hasOne relationship)
+ * ['project.[id, name]'] -> will return an object with a project property which has an
+ * id and name property containing the project name
+ * (hasOne relationship)
+ * ['tags.name'] -> will return an object with an array `tags` property containing
+ * objects each with a single property: name
+ *
+ * @param tx database transaction to use, leave out to start a new one
+ * @param props a property specification
+ * @param callback(result)
+ */
+ Entity.prototype.selectJSON = function(tx, props, callback) {
+ var that = this;
+ var args = argspec.getArgs(arguments, [
+ { name: "tx", optional: true, check: persistence.isTransaction, defaultValue: null },
+ { name: "props", optional: false },
+ { name: "callback", optional: false }
+ ]);
+ tx = args.tx;
+ props = args.props;
+ callback = args.callback;
+
+ if(!tx) {
+ this._session.transaction(function(tx) {
+ that.selectJSON(tx, props, callback);
+ });
+ return;
+ }
+ var includeProperties = {};
+ props.forEach(function(prop) {
+ var current = includeProperties;
+ var parts = prop.split('.');
+ for(var i = 0; i < parts.length; i++) {
+ var part = parts[i];
+ if(i === parts.length-1) {
+ if(part === '*') {
+ current.id = true;
+ for(var p in meta.fields) {
+ if(meta.fields.hasOwnProperty(p)) {
+ current[p] = true;
+ }
+ }
+ for(var p in meta.hasOne) {
+ if(meta.hasOne.hasOwnProperty(p)) {
+ current[p] = true;
+ }
+ }
+ for(var p in meta.hasMany) {
+ if(meta.hasMany.hasOwnProperty(p)) {
+ current[p] = true;
+ }
+ }
+ } else if(part[0] === '[') {
+ part = part.substring(1, part.length-1);
+ var propList = part.split(/,\s*/);
+ propList.forEach(function(prop) {
+ current[prop] = true;
+ });
+ } else {
+ current[part] = true;
+ }
+ } else {
+ current[part] = current[part] || {};
+ current = current[part];
+ }
+ }
+ });
+ this.buildJSON(tx, includeProperties, callback);
+ };
+
+ Entity.prototype.buildJSON = function(tx, includeProperties, callback) {
+ var session = this._session;
+ var properties = [];
+ var fieldSpec = meta.fields;
+ var that = this;
+
+ for(var p in includeProperties) {
+ if(includeProperties.hasOwnProperty(p)) {
+ properties.push(p);
+ }
+ }
+
+ var cheapProperties = [];
+ var expensiveProperties = [];
+
+ properties.forEach(function(p) {
+ if(includeProperties[p] === true && !meta.hasMany[p]) { // simple, loaded field
+ cheapProperties.push(p);
+ } else {
+ expensiveProperties.push(p);
+ }
+ });
+
+ var itemData = this._data;
+ var item = {};
+
+ cheapProperties.forEach(function(p) {
+ if(p === 'id') {
+ item.id = that.id;
+ } else if(meta.hasOne[p]) {
+ item[p] = {id: itemData[p]};
+ } else {
+ item[p] = persistence.entityValToJson(itemData[p], fieldSpec[p]);
+ }
+ });
+ properties = expensiveProperties.slice();
+
+ function processOneProperty() {
+ var p = properties.pop();
+
+ if(meta.hasOne[p]) {
+ that.fetch(session, tx, p, function(obj) {
+ obj.buildJSON(tx, includeProperties[p], function(result) {
+ item[p] = result;
+ if(properties.length > 0) {
+ processOneProperty();
+ } else {
+ callback(item);
+ }
+ });
+ });
+ } else if(meta.hasMany[p]) {
+ persistence.get(that, p).list(function(objs) {
+ item[p] = [];
+ function oneObj() {
+ var obj = objs.pop();
+ function next() {
+ if(objs.length > 0) {
+ oneObj();
+ } else {
+ if(properties.length > 0) {
+ processOneProperty();
+ } else {
+ callback(item);
+ }
+ }
+ }
+ if(includeProperties[p] === true) {
+ item[p].push({id: obj.id});
+ next();
+ } else {
+ obj.buildJSON(tx, includeProperties[p], function(result) {
+ item[p].push(result);
+ next();
+ });
+ }
+ }
+ if(objs.length > 0) {
+ oneObj();
+ } else {
+ if(properties.length > 0) {
+ processOneProperty();
+ } else {
+ callback(item);
+ }
+ }
+ });
+ }
+ }
+ if(properties.length > 0) {
+ processOneProperty();
+ } else {
+ callback(item);
+ }
+ };
+
Entity.prototype.fetch = function(session, tx, rel, callback) {
var args = argspec.getArgs(arguments, [
{ name: 'session', optional: true, check: persistence.isSession, defaultValue: persistence },
@@ -586,7 +761,11 @@ persistence.get = function(arg1, arg2) {
if(type) {
switch(type) {
case 'DATE':
- return Math.round(value.getTime() / 1000);
+ if(value) {
+ return Math.round(value.getTime() / 1000);
+ } else {
+ return null;
+ }
break;
default:
return value;

0 comments on commit d714e0d

Please sign in to comment.