Model Schema - gregerolsson/booster GitHub Wiki
It is not uncommon for models to have nested models and collections which are based on attributes in the model that are received from the server. Consider the following data structure:
post = {
id: 123,
title: "A blog post",
comments: [{
by: 123,
at: "2011-12-16T13:27:11.146Z"
text: "Nice post"
}]
}
Likely we have a Backbone.Model
representing blog posts, a Backbone.Model
representing comments, and a Backbone.Collection
representing collections
of comments.
When instantiating a blog post model over the post data, and invoking get('comments')
you will get the actual array of objects stored in the comments
attribute, not a
Backbone.Collection
. There are several ways of dealing with this outside of Backbone,
and in Booster we have chosen to supply a mixin that allows you to define a small
schema describing how your model is built up:
app/assets/javascripts/models/post.js.booster
var schema = require('booster/support/schema'),
comment = require(./comment);
exports.Model = Backbone.Model.extend({
schema: {
comments: comment.Collection
}
});
_.extend(exports.Model.prototype, schema.mixin());
Applying the schema
mixin to the model will make the internal get
and set
methods aware of any schema definition in the model. When invoking get
to fetch
and attribute from the model, the schema is examined and if an entry for the requested
attribute is found, it will perform a conversion, among other things.
So the following code will work:
app/assets/javascripts/examples/an_example.js.booster
var post = require('models/post');
var data = {
id: 123,
title: "A blog post",
comments: [{
by: 123,
at: "2011-12-16T13:27:11.146Z"
text: "Nice post"
}]
}
model = new post.Model(data);
console.log(model.get('comments').map(function(comment) {
return comment.get('text');
}));
Here when invoking get('comments')
, get
recognizes that the schema says that the comments
attribute should be converted into a comment collection. The comments array is automatically
converted into a comments collection which is memoized inside the post instance -- invoking
get('comments')
again on the same post model will return the same collection instance.
Invoking model.toJSON()
in the example above will also do the right thing; the comments
array
in the resulting object will come from the memoized comments collection, not the old comments
attribute
in the original model data. In fact, when the comments
array is converted to a collection the actual
attribute value is removed from the model since the collection takes ownership of the data and there
is no need to keep a potentially stale copy of it around.
More Advanced Schemas
The simplest form of defining a schema is to simply list the direct conversion you want for an attribute, as in the example above:
schema: {
comments: comment.Collection
}
The Booster schema mixin can do more than that, though, and mimics the API used in Mongoose for those that have used it together with Node.js to access MongoDB databases. Below are a few example of what is supported.
Instructing the toJSON Behavior
schema: {
comments: { type: comment.Collection, serialize: false }
}
Instructs toJSON
not to include the comments
attribute.
Consider loading a blog post from a REST-API which is backed by a relational database where comments are stored in a separate table. It is not unlikely that the API exposes the resources similar to the following to deal with posts and comments:
- GET /posts
- POST /posts
- GET /posts/:id
- PUT /posts/:id
- POST /posts/:id/comments
- PUT /comments/:id
Fetching an individual post through GET /posts/123
will probably include all the comments for that
post which are resolved in the API through a join. Adding a new comment to the post is here achieved
by a POST /posts/123/comments
, not by adding a comment model to the comments collection of the post
and saving the whole post. In fact PUT /posts/123
is likely to only deal with the post attributes
like title
and body
, but still GET /posts/123
includes the comments since that is probably what
the clients of the API need from the request.
This is where the serialize
attribute in the schema comes in. When performing a save
on the
post model (which performs a toJSON
internally) the comments array will not be sent to the server
which is what we want for this particular REST-API.
Another example:
exports.Model = Backbone.Model.extend({
schema: {
comments: { type: comment.Collection, serialize: id }
}
);
function id(model) {
return model.id;
}
Instructs toJSON
to invokde the id
function for every model in the collection.
If your REST-API is backed by a NoSQL database like MongoDB and you have a document that
references other top-level documents through an array of ObjectIDs the example above will
fit your bill. The comments collection nested in the post will be mapped using the given
function id
when toJSON
is invoked, effectively transforming it to an array of IDs.
All of the above also works for nested models:
exports.Model = Backbone.Model.extend({
schema: {
by: { type: user.Model, serialize: id },
comments: { type: comment.Collection, serialize: id }
}
);
function id(model) {
return model.id;
}
Validation
To be defined.