Backbone-relational provides one-to-one, one-to-many and many-to-one relations between models for Backbone. To use relations, extend Backbone.RelationalModel
(instead of the regular Backbone.Model
) and define a property relations
, containing an array of option objects. Each relation must define (as a minimum) the type
, key
and relatedModel
. Available relation types are Backbone.HasOne
and Backbone.HasMany
.
- Bi-directional relations automatically notify related models of changes.
- Decide how relations are serialized using the
includeInJSON
option (just id, or the full set of attributes, in which case the relations of this object are in turn serialized as well). - Convert nested objects in a model's attributes into Models when using the
createModels
option upon initialization. - Bind new events to a RelationalModel for:
- addition/removal on HasMany relations (bind to
add:<key>
andremove:<key>
; arguments=(model, collection)
), - changes to the key itself on HasMany and HasOne relations (bind to
update:<key>
; arguments=(model, attribute)
).
- addition/removal on HasMany relations (bind to
Backbone-relational depends on backbone (and thus on underscore). Please use a HEAD version of backbone.js, since the latest stable is quite old.
paul = new Person({
id: 'person-1',
name: 'Paul',
user: { id: 'user-1', login: 'dude', email: 'me@gmail.com' }
});
// A User object is automatically created from the JSON; so 'login' returns 'dude'.
paul.get('user').get('login');
ourHouse = new House({
id: 'house-1',
location: 'in the middle of the street',
occupants: ['person-1']
});
// 'ourHouse.occupants' is turned into a Backbone.Collection of Persons.
// The first person in 'ourHouse.occupants' will point to 'paul'.
ourHouse.occupants.at(0); // === paul
// the relation from 'House.occupants' to 'Person' has been defined as a bi-directional HasMany relation,
// with a reverse relation to 'Person.livesIn'. So, 'paul.livesIn' will automatically point back to 'ourHouse'.
paul.get('livesIn'); // === ourHouse
// You can control which relations get serialized to JSON (when saving), using the 'includeInJSON'
// property on a Relation. Also, each object will only get serialized once to prevent loops.
paul.get('user').toJSON();
/* result:
{
email: 'me@gmail.com',
id: 'user-1',
login: 'dude',
person: {
id: 'person-1',
name: 'Paul',
livesIn: {
id: "house-1",
location: "in the middle of the street",
occupants: ["person-1"] // not serialized because 'includeInJSON' is false
},
user: 'user-1' // not serialized because it is already in the JSON, so we won't create a loop
}
}
*/
// Use the 'add' and 'remove' events to listen for additions/removals on HasMany relations (like 'House.occupants').
ourHouse.bind( 'add:occupants', function( model, coll ) {
// create a View?
console.debug( 'add %o', model );
});
ourHouse.bind( 'remove:occupants', function( model, coll ) {
// destroy a View?
console.debug( 'remove %o', model );
});
// Use the 'update' event to listen for changes on a HasOne relation (like 'Person.livesIn').
paul.bind( 'update:livesIn', function( model, attr ) {
console.debug( 'update to %o', attr );
});
// Modifying either side of a bi-directional relation updates the other side automatically.
// Make paul homeless; triggers 'remove:occupants' on ourHouse, and 'update:livesIn' on paul
ourHouse.get('occupants').remove( paul.id );
paul.get('livesIn'); // yup; nothing.
// Move back in; triggers 'add:occupants' on ourHouse, and 'update:livesIn' on paul
paul.set( { 'livesIn': 'house-1' } );
This is achieved using the following relations and models:
House = Backbone.RelationalModel.extend({
// The 'relations' property, on the House's prototype. Initialized separately for each instance of House.
// Each relation must define (as a minimum) the 'type', 'key' and 'relatedModel'. Options are
// 'includeInJSON', 'createModels' and 'reverseRelation', which takes the same options as the relation itself.
relations: [
{
type: Backbone.HasMany, // Use the type, or the string 'HasOne' or 'HasMany'.
key: 'occupants',
relatedModel: 'Person',
includeInJSON: false,
reverseRelation: {
key: 'livesIn'
}
}
]
});
Person = Backbone.RelationalModel.extend({
relations: [
{ // Create a (recursive) one-to-one relationship
type: Backbone.HasOne,
key: 'user',
relatedModel: 'User',
reverseRelation: {
type: Backbone.HasOne,
key: 'person'
}
}
],
initialize: function() {
// do whatever you want :)
}
});
User = Backbone.RelationalModel.extend();
The key for a HasOne relation consists of a single Backbone.RelationalModel
. The default reverseRelation.type
for a HasOne relation is HasMany. This can be configured to be HasOne
instead, to create one-to-one relations.
The key for a HasMany relation consists of a Backbone.Collection
of Backbone.RelationalModel
s. The default reverseRelation.type
for a HasMany relation is HasOne; this is the only option here, since many-to-many is not supported directly.
A many-to-many relation can be modeled using two HasMany relations:
Person = Backbone.RelationalModel.extend({
relations: [
{
type: 'HasMany',
key: 'jobs',
relatedModel: 'Tenure',
reverseRelation: {
key: 'person'
}
}
]
});
// A link object between 'Person' and 'Company', to achieve many-to-many relations.
Tenure = Backbone.RelationalModel.extend({
defaults: {
'startDate': null,
'endDate': null
}
})
Company = Backbone.RelationalModel.extend({
relations: [
{
type: 'HasMany',
key: 'employees',
relatedModel: 'Tenure',
reverseRelation: {
key: 'company'
}
}
]
});
niceCompany = new Company( { name: 'niceCompany' } );
niceCompany.bind( 'add:employees', function( model, coll ) {
// Will see a Tenure with attributes { person: paul, company: niceCompany } being added here
});
paul.get('jobs').add( { company: niceCompany } );
Each Backbone.RelationalModel
registers itself with Backbone.Store
upon creation (and removes itself from the Store
when destroyed). When creating or updating an attribute that is a key in a relation, removed related objects are notified of their removal, and new related objects are looked up in the Store
.