PageRenderTime 66ms CodeModel.GetById 49ms app.highlight 14ms RepoModel.GetById 0ms app.codeStats 0ms

/README.md

https://github.com/cjroebuck/Backbone-relational
Markdown | 311 lines | 237 code | 74 blank | 0 comment | 0 complexity | 921c84c5903562b6a3b52f7f528fd0c2 MD5 | raw file
  1# Backbone-relational
  2Backbone-relational provides one-to-one, one-to-many and many-to-one relations between models for [Backbone](https://github.com/documentcloud/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`. Backbone-relational features:
  3
  4* Bidirectional relations, which notify related models of changes through events.
  5* Control how relations are serialized using the `includeInJSON` option.
  6* Automatically convert nested objects in a model's attributes into Model instances using the `createModels` option.
  7* Retrieve (a set of) related models through the `fetchRelated(key<string>, [options<object>])` method.
  8* Determine the type of `HasMany` collections with `collectionType`.
  9* Bind new events to a `Backbone.RelationalModel` for:
 10	* addition to a `HasMany` relation (bind to `add:<key>`; arguments: `(addedModel, relatedCollection)`),
 11	* removal from a `HasMany` relation (bind to `remove:<key>`; arguments: `(removedModel, relatedCollection)`),
 12	* changes to the key itself on `HasMany` and `HasOne` relations (bind to `update:<key>`; arguments=`(model, relatedModel/relatedCollection)`).
 13
 14## Contents
 15
 16* [Installation](#installation)
 17* [Backbone.Relation options](#backbone-relation)
 18* [Backbone.RelationalModel](#backbone-relationalmodel)
 19* [Example](#example)
 20* [Under the hood](#under-the-hood)
 21
 22## <a name="installation"/>Installation
 23
 24Backbone-relational depends on [backbone](https://github.com/documentcloud/backbone) (and thus on  [underscore](https://github.com/documentcloud/underscore)). Include Backbone-relational right after Backbone and Underscore:
 25
 26```javascript
 27<script type="text/javascript" src="./js/underscore.js"></script>
 28<script type="text/javascript" src="./js/backbone.js"></script>
 29<script type="text/javascript" src="./js/backbone-relational.js"></script>
 30```
 31
 32Backbone-relational has been tested with Backbone 0.5.1 (or newer) and Underscore 1.1.6 (or newer).
 33
 34## <a name="backbone-relation"/>Backbone.Relation options
 35
 36Each `Backbone.RelationalModel` can contain an array of `relations`. Each relation supports a number of options, of which `relatedModel`, `key` and `type` are mandatory. A relation could look like the following:
 37
 38```javascript
 39Zoo = Backbone.RelationalModel.extend({
 40	relations: [{
 41			type: Backbone.HasMany,
 42			key: 'animals',
 43			relatedModel: 'Animal',
 44			collectionType: 'AnimalCollection',
 45			reverseRelation: {
 46				key: 'livesIn',
 47				includeInJSON: 'id'
 48				// 'relatedModel' is automatically set to 'Zoo'; the 'relationType' to 'HasOne'.
 49			}
 50		}]
 51});
 52
 53Animal = Backbone.RelationalModel.extend({
 54	urlRoot: '/animal/'
 55});
 56
 57AnimalCollection = Backbone.Collection.extend({
 58	model: Animal,
 59	
 60	url: function( models ) {
 61		return '/animal/' + ( models ? 'set/' + _.pluck( models, 'id' ).join(';') + '/' : '' );
 62	}
 63});
 64```
 65
 66### relatedModel
 67
 68Value: a string (which can be resolved to an object type on the global scope), or a reference to a `Backbone.RelationalModel` type.
 69
 70### key
 71
 72Value: a string. References an attribute name on `relatedModel`.
 73
 74### type
 75
 76Value: a string, or a reference to a `Backbone.Relation` type
 77
 78Example: `Backbone.HasOne` or `'HasMany'`.
 79
 80#### HasOne relations (`Backbone.HasOne`)
 81
 82The key for a `HasOne` relation consists of a single `Backbone.RelationalModel`. The default `reverseRelation.type` for a HasOne relation is HasMany. This can be set to `HasOne` instead, to create a one-to-one relation.
 83
 84#### HasMany relations (`Backbone.HasMany`)
 85
 86The key for a `HasMany` relation consists of a `Backbone.Collection`, containing zero or more `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.
 87
 88#### <a name="many-to-many"/>Many-to-many relations
 89A many-to-many relation can be modeled using two `Backbone.HasMany` relations, with a link model in between:
 90
 91```javascript
 92Person = Backbone.RelationalModel.extend({
 93	relations: [
 94		{
 95			type: 'HasMany',
 96			key: 'jobs',
 97			relatedModel: 'Job',
 98			reverseRelation: {
 99				key: 'person'
100			}
101		}
102	]
103});
104
105// A link object between 'Person' and 'Company', to achieve many-to-many relations.
106Job = Backbone.RelationalModel.extend({
107	defaults: {
108		'startDate': null,
109		'endDate': null
110	}
111})
112
113Company = Backbone.RelationalModel.extend({
114	relations: [
115		{
116			type: 'HasMany',
117			key: 'employees',
118			relatedModel: 'Job',
119			reverseRelation: {
120				key: 'company'
121			}
122		}
123	]
124});
125
126niceCompany = new Company( { name: 'niceCompany' } );
127niceCompany.bind( 'add:employees', function( model, coll ) {
128		// Will see a Job with attributes { person: paul, company: niceCompany } being added here
129	});
130
131paul.get('jobs').add( { company: niceCompany } );
132```
133
134### collectionType
135
136Value: a string (which can be resolved to an object type on the global scope), or a reference to a `Backbone.Collection` type.
137
138Determine the type of collections used for a `HasMany` relation. Defining a `url(models<Backbone.Model[]>)` function on this Collection that's able to build a url for either the whole collection, or a set of models enables `fetchRelated` to fetch all missing models in one request, instead of firing a separate request for each. See [Backbone-tastypie](https://github.com/PaulUithol/backbone-tastypie/blob/master/backbone-tastypie.js#L74) for an example.
139
140### includeInJSON
141
142Value: a boolean, or a string referencing one of the model's attributes. Default: `true`.
143
144Determines how a relation will be serialized following a call to the `toJSON` method. A value of `true` serializes the full set of attributes on the related model(s), in which case the relations of this object are serialized as well. Set to `false` to exclude the relation completely. You can also choose to include a single attribute from the related model by using a string. For example, `'name'`, or `Backbone.Model.prototype.idAttribute` to include ids.
145
146### createModels
147
148Value: a boolean. Default: `true`.
149
150Should models be created from nested objects, or not?
151
152### reverseRelation
153
154If the relation should be bidirectional, specify the details for the reverse relation here. It's only mandatory to supply a `key`; `relatedModel` is automatically set. The default `type` for a `reverseRelation` is `HasMany` for a `HasOne` relation (which can be overridden to `HasOne` in order to create a one-to-one relation), and `HasOne` for a `HasMany` relation. In this case, you cannot create a reverseRelation with type `HasMany` as well; please see [Many-to-many relations](#many-to-many) on how to model these type of relations.
155
156**Please note**: if you define a relation (plus a `reverseRelation`) on a model, but never actually create an instance of that model, the model's `constructor` will never run, which means it's `initializeRelations` will never get called, and the reverseRelation will not be initialized either. In that case, you could either define the relation on the opposite model, or define two single relations. See [issue 20](https://github.com/PaulUithol/Backbone-relational/issues/20) for a discussion.
157
158## <a name="backbone-relationalmodel"/>Backbone.RelationalModel
159
160`Backbone.RelationalModel` introduces a couple of new methods and events.
161
162### Methods
163
164#### getRelations `relationalModel.getRelations()`
165
166Returns the set of initialized relations on the model.
167
168#### fetchRelated `relationalModel.fetchRelated(key<string>, [options<object>])`
169
170Fetch models from the server that were referenced in the model's attributes, but have not been found/created yet. This can be used specifically for lazy-loading scenarios.
171
172By default, a separate request will be fired for each additional model that is to be fetched from the server. However, if your server/API supports it, you can fetch the set of models in one request by specifying a `collectionType` for the relation you call `fetchRelated` on. The `collectionType` should have an overridden `url(models<Backbone.Model[]>)` method that allows it to construct a url for an array of models. See the example at the top of [Backbone.Relation options](#backbone-relation) or [Backbone-tastypie](https://github.com/PaulUithol/backbone-tastypie/blob/master/backbone-tastypie.js#L74) for an example.
173
174### Events
175
176* `add`: triggered on addition to a `HasMany` relation.  
177  Bind to `add:<key>`; arguments: `(addedModel<Backbone.Model>, related<Backbone.Collection>)`.
178* `remove`: triggered on removal from a `HasMany` relation.  
179  Bind to `remove:<key>`; arguments: `(removedModel<Backbone.Model>, related<Backbone.Collection>)`.
180* `update`: triggered on changes to the key itself on `HasMany` and `HasOne` relations.  
181  Bind to `update:<key>`; arguments: `(model<Backbone.Model>, related<Backbone.Model|Backbone.Collection>)`.
182
183## <a name="example"/>Example
184
185```javascript
186paul = new Person({
187	id: 'person-1',
188	name: 'Paul',
189	user: { id: 'user-1', login: 'dude', email: 'me@gmail.com' }
190});
191
192// A User object is automatically created from the JSON; so 'login' returns 'dude'.
193paul.get('user').get('login');
194
195ourHouse = new House({
196	id: 'house-1',
197	location: 'in the middle of the street',
198	occupants: ['person-1', 'person-2', 'person-5']
199});
200
201// 'ourHouse.occupants' is turned into a Backbone.Collection of Persons.
202// The first person in 'ourHouse.occupants' will point to 'paul'.
203ourHouse.get('occupants').at(0); // === paul
204
205// the relation from 'House.occupants' to 'Person' has been defined as a bi-directional HasMany relation,
206// with a reverse relation to 'Person.livesIn'. So, 'paul.livesIn' will automatically point back to 'ourHouse'.
207paul.get('livesIn'); // === ourHouse
208
209// You can control which relations get serialized to JSON (when saving), using the 'includeInJSON'
210// property on a Relation. Also, each object will only get serialized once to prevent loops.
211paul.get('user').toJSON();
212	/* result:
213		{
214			email: "me@gmail.com",
215			id: "user-1",
216			login: "dude",
217			person: {
218				id: "person-1",
219				name: "Paul",
220				livesIn: {
221					id: "house-1",	
222					location: "in the middle of the street",
223					occupants: ["person-1"] // just the id, since 'includeInJSON' references the 'idAttribute'
224				},
225				user: "user-1" // not serialized because it is already in the JSON, so we won't create a loop
226			}
227		}
228	*/
229
230// Load occupants 'person-2' and 'person-5', which don't exist yet, from the server
231ourHouse.fetchRelated( 'occupants' );
232
233// Use the 'add' and 'remove' events to listen for additions/removals on HasMany relations (like 'House.occupants').
234ourHouse.bind( 'add:occupants', function( model, coll ) {
235		// create a View?
236		console.debug( 'add %o', model );
237	});
238ourHouse.bind( 'remove:occupants', function( model, coll ) {
239		// destroy a View?
240		console.debug( 'remove %o', model );
241	});
242
243// Use the 'update' event to listen for changes on a HasOne relation (like 'Person.livesIn').
244paul.bind( 'update:livesIn', function( model, attr ) {
245		console.debug( 'update to %o', attr );
246	});
247
248
249// Modifying either side of a bi-directional relation updates the other side automatically.
250// Make paul homeless; triggers 'remove:occupants' on ourHouse, and 'update:livesIn' on paul
251ourHouse.get('occupants').remove( paul.id ); 
252
253paul.get('livesIn'); // yup; nothing.
254
255// Move back in; triggers 'add:occupants' on ourHouse, and 'update:livesIn' on paul
256paul.set( { 'livesIn': 'house-1' } );
257```
258
259This is achieved using the following relations and models:
260
261```javascript
262House = Backbone.RelationalModel.extend({
263	// The 'relations' property, on the House's prototype. Initialized separately for each instance of House.
264	// Each relation must define (as a minimum) the 'type', 'key' and 'relatedModel'. Options are
265	// 'includeInJSON', 'createModels' and 'reverseRelation', which takes the same options as the relation itself.
266	relations: [
267		{
268			type: Backbone.HasMany, // Use the type, or the string 'HasOne' or 'HasMany'.
269			key: 'occupants',
270			relatedModel: 'Person',
271			includeInJSON: Backbone.Model.prototype.idAttribute,
272			collectionType: 'PersonCollection',
273			reverseRelation: {
274				key: 'livesIn'
275			}
276		}
277	]
278});
279
280Person = Backbone.RelationalModel.extend({
281	relations: [
282		{ // Create a (recursive) one-to-one relationship
283			type: Backbone.HasOne,
284			key: 'user',
285			relatedModel: 'User',
286			reverseRelation: {
287				type: Backbone.HasOne,
288				key: 'person'
289			}
290		}
291	],
292	
293	initialize: function() {
294		// do whatever you want :)
295	}
296});
297
298PersonCollection = Backbone.Collection.extend({
299	url: function( models ) {
300		// Logic to create a url for the whole collection, or a set of models.
301		// See the tests, or Backbone-tastypie, for an example.
302		return '/person/' + ( models ? 'set/' + _.pluck( models, 'id' ).join(';') + '/' : '' );
303	}
304});
305
306User = Backbone.RelationalModel.extend();
307```
308
309## <a name="under-the-hood"/>Under the hood
310
311Each `Backbone.RelationalModel` registers itself with `Backbone.Store` upon creation (and is removed 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`.