Skip to content

Commit

Permalink
Added selectJSON method to entity objects, see README.md for
Browse files Browse the repository at this point in the history
documentation.
  • Loading branch information
zefhemel committed Sep 22, 2010
1 parent 08ab596 commit d714e0d
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 1 deletion.
29 changes: 29 additions & 0 deletions README.md
Expand Up @@ -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
-----------------

Expand Down
181 changes: 180 additions & 1 deletion lib/persistence.js
Expand Up @@ -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 },
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit d714e0d

Please sign in to comment.