Permalink
Browse files

Merge branch 'master' of git://github.com/PaulUithol/Backbone-relatio…

…nal into submodels

Conflicts:
	backbone-relational.js
  • Loading branch information...
2 parents 7426efc + e7d0ac3 commit c30c3de15b9321972bf386b9c4d7f415558196f2 @DouweM committed Apr 7, 2012
Showing with 139 additions and 23 deletions.
  1. +2 −0 .gitignore
  2. +57 −7 README.md
  3. +26 −10 backbone-relational.js
  4. +54 −6 test/tests.js
View
@@ -1,2 +1,4 @@
.idea
node_modules
+
+.DS_Store
View
@@ -14,14 +14,22 @@ Backbone-relational provides one-to-one, one-to-many and many-to-one relations b
## Contents
-* [Installation](#installation)
+* [Getting started](#getting-started)
* [Backbone.Relation options](#backbone-relation)
* [Backbone.RelationalModel](#backbone-relationalmodel)
* [Example](#example)
* [Known problems and solutions](#q-and-a)
* [Under the hood](#under-the-hood)
-## <a name="installation"/>Installation
+
+## <a name="getting-started"/>Getting started
+
+Resources to get you started with Backbone-relational:
+
+* [A great tutorial by antoviaque](http://antoviaque.org/docs/tutorials/backbone-relational-tutorial/) ([and the accompanying git repository](https://github.com/antoviaque/backbone-relational-tutorial))
+
+
+### <a name="installation"/>Installation
Backbone-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:
@@ -33,6 +41,7 @@ Backbone-relational depends on [backbone](https://github.com/documentcloud/backb
Backbone-relational has been tested with Backbone 0.9.0 (or newer) and Underscore 1.3.1 (or newer).
+
## <a name="backbone-relation"/>Backbone.Relation options
Each `Backbone.RelationalModel` can contain an array of `relations`.
@@ -146,12 +155,26 @@ For example, a Rails backend may provide the keys suffixed with `_id` or `_ids`.
1. When a relation is instantiated, the contents of the `keySource` are used as it's initial data.
2. The application uses the regular `key` attribute to interface with the relation and the models in it; the `keySource` is not available as an attribute for the model.
-3. When calling `toJSON` on a model (either via `Backbone.sync`, or directly), the data in the `key` attribute is tranformed and assigned to the `keySource`.
So you may be provided with data containing `animal_ids`, while you want to access this relation as `zoo.get( 'animals' );`.
-When saving `zoo`, the `animals` attribute will be serialized back into the `animal_ids` key.
-**WARNING**: when using a `keySource`, you should refrain from using that attribute name for other purposes.
+**NOTE**: for backward compatibility reasons, setting `keySource` will set `keyDestination` as well.
+This means that when saving `zoo`, the `animals` attribute will be serialized back into the `animal_ids` key.
+
+**WARNING**: when using a `keySource`, you should not use that attribute name for other purposes.
+
+### keyDestination
+
+Value: a string. References an attribute to serialize `relatedModel` into.
+
+Used to override `key` (and `keySource`) when determining what attribute to be written into when serializing a relation, since the server backing your relations may use different naming conventions.
+For example, a Rails backend may expect the keys to be suffixed with `_attributes` for nested attributes.
+
+When calling `toJSON` on a model (either via `Backbone.sync`, or directly), the data in the `key` attribute is transformed and assigned to the `keyDestination`.
+
+So you may want a relation to be serialized into the `animals_attributes` key, while you want to access this relation as `zoo.get( 'animals' );`.
+
+**WARNING**: when using a `keyDestination`, you should not use that attribute name for other purposes.
### collectionType
@@ -171,6 +194,13 @@ By default, the relation's `key` attribute will be used to create a reference to
If you set `collectionKey` to a string, it will use that string as the reference to the RelationalModel, rather than the relation's `key` attribute.
If you don't want this behavior at all, set `collectionKey` to false (or any falsy value) and this reference will not be created.
+### collectionOptions
+
+Value: an options hash or a function that accepts an instance of a `Backbone.RelationalModed` and returns an option hash
+
+Used to provide options for the initialization of the collection in the "Many"-end of a `HasMany` relation. Can be an options hash or
+a function that should take the instance in the "One"-end of the "HasMany" relation and return an options hash
+
### includeInJSON
Value: a boolean, or a string referencing one of the model's attributes. Default: `true`.
@@ -404,9 +434,29 @@ User = Backbone.RelationalModel.extend();
## <a name="q-and-a"/>Known problems and solutions
-> **Q:** After a fetch, `add:<key>` events don't occur for nested relations.
+> **Q:** (Reverse) relations don't seem to be initialized properly (and I'm using Coffeescript!)
+
+**A:** You're probably using the syntax `class MyModel extends Backbone.RelationalModel` instead of `MyModel = Backbone.RelationalModel.extend`.
+This has advantages in CoffeeScript, but it also means that `Backbone.Model.extend` will not get called.
+Instead, CoffeeScript generates piece of code that would normally achieve roughly the same.
+However, `extend` is also the method that Backbone-relational overrides to set up relations as soon as your code gets parsed by the JavaScript engine.
+
+A possible solution is to initialize a blank placeholder model right after defining a model that contains reverseRelations; this will also bootstrap the relations. For example:
+
+```javascript
+class MyModel extends Backbone.RelationalModel
+ relations: [
+ // etc
+ ]
+
+new MyModel
+```
+
+See [issue #91](https://github.com/PaulUithol/Backbone-relational/issues/91) for more information and workarounds.
+
+> **Q:** After a fetch, I don't get `add:<key>` events for nested relations.
-**A:** This is due to the `{silent: true}` in `Backbone.Collection.reset`. Pass `fetch( {add: true} )` to bypass this problem.
+**A:** This is due to `Backbone.Collection.reset` silencing add events. Pass `fetch( {add: true} )` to bypass this problem.
You may want to override `Backbone.Collection.fetch` for this, and also trigger an event when the fetch has finished while you're at it.
Example:
View
@@ -292,6 +292,7 @@
this.key = this.options.key;
this.keySource = this.options.keySource || this.key;
+ this.keyDestination = this.options.keyDestination || this.options.keySource || this.key;
// 'exports' should be the global object where 'relatedModel' can be found on if given as a string.
this.relatedModel = this.options.relatedModel;
@@ -738,7 +739,8 @@
options: {
reverseRelation: { type: 'HasOne' },
collectionType: Backbone.Collection,
- collectionKey: true
+ collectionKey: true,
+ collectionOptions: {}
},
initialize: function() {
@@ -753,8 +755,8 @@
if ( !this.collectionType.prototype instanceof Backbone.Collection.prototype.constructor ){
throw new Error( 'collectionType must inherit from Backbone.Collection' );
}
-
- this.setRelated( this.prepareCollection( new this.collectionType() ) );
+
+ this.setRelated( this._prepareCollection() );
this.findRelated( { silent: true } );
},
@@ -767,14 +769,28 @@
return model;
},
- prepareCollection: function( collection ) {
+ _getCollectionOptions: function() {
+ return _.isFunction( this.options.collectionOptions ) ?
+ this.options.collectionOptions( this.instance ) :
+ this.options.collectionOptions;
+ },
+
+ /**
+ * Bind events and setup collectionKeys for a collection that is to be used as the backing store for a HasMany.
+ * @param {Backbone.Collection} [collection]
+ */
+ _prepareCollection: function( collection ) {
if ( this.related ) {
this.related
.unbind( 'relational:add', this.handleAddition )
.unbind( 'relational:remove', this.handleRemoval )
.unbind( 'relational:reset', this.handleReset )
}
+ if ( !collection || !( collection instanceof Backbone.Collection ) ) {
+ collection = new this.collectionType( [], this._getCollectionOptions() );
+ }
+
// If we have a modelBuilder, make sure this is used to build models
// from objects passed directly to the collection as well.
if ( this.modelBuilder && typeof this.modelBuilder === "function" ) {
@@ -854,7 +870,7 @@
// Replace 'this.related' by 'attr' if it is a Backbone.Collection
if ( attr instanceof Backbone.Collection ) {
- this.prepareCollection( attr );
+ this._prepareCollection( attr );
this.related = attr;
}
// Otherwise, 'attr' should be an array of related object ids.
@@ -868,7 +884,7 @@
coll.reset( [], { silent: true } );
}
else {
- coll = this.prepareCollection( new this.collectionType() );
+ coll = this._prepareCollection();
}
this.setRelated( coll );
@@ -1300,21 +1316,21 @@
var value = json[ rel.key ];
if ( rel.options.includeInJSON === true && value && _.isFunction( value.toJSON ) ) {
- json[ rel.keySource ] = value.toJSON();
+ json[ rel.keyDestination ] = value.toJSON();
}
else if ( _.isString( rel.options.includeInJSON ) ) {
if ( value instanceof Backbone.Collection ) {
- json[ rel.keySource ] = value.pluck( rel.options.includeInJSON );
+ json[ rel.keyDestination ] = value.pluck( rel.options.includeInJSON );
}
else if ( value instanceof Backbone.Model ) {
- json[ rel.keySource ] = value.get( rel.options.includeInJSON );
+ json[ rel.keyDestination ] = value.get( rel.options.includeInJSON );
}
}
else {
delete json[ rel.key ];
}
- if ( rel.keySource !== rel.key ) {
+ if ( rel.keyDestination !== rel.key ) {
delete json[ rel.key ];
}
}, this );
View
@@ -51,6 +51,7 @@ $(document).ready(function() {
key: 'animals',
relatedModel: 'Animal',
collectionType: 'AnimalCollection',
+ collectionOptions: function( instance ) { return { 'url': 'zoo/' + instance.cid + '/animal/' } },
reverseRelation: {
key: 'livesIn',
includeInJSON: 'id'
@@ -76,7 +77,12 @@ $(document).ready(function() {
});
window.AnimalCollection = Backbone.Collection.extend({
- model: Animal
+ model: Animal,
+
+ initialize: function( models, options ) {
+ options || (options = {});
+ this.url = options.url;
+ }
});
window.Visitor = Backbone.RelationalModel.extend();
@@ -677,7 +683,7 @@ $(document).ready(function() {
ok( person.get( 'user' ).get( 'resource_uri' ) == null );
});
- test( "'keySource' loads from & saves to 'key'", function() {
+ test( "'keySource' loads from 'key", function() {
var Property = Backbone.RelationalModel.extend({
idAttribute: 'property_id'
});
@@ -717,12 +723,54 @@ $(document).ready(function() {
// The values from view.property_ids should be loaded into view.properties
ok( view.get( 'properties' ) && view.get( 'properties' ).length === 2, "'view' has two 'properties'" );
ok( typeof view.get( 'property_ids' ) === 'undefined', "'view' does not have 'property_ids'" );
+ });
+
+ test( "'keyDestination' saves to 'key'", function() {
+ var Property = Backbone.RelationalModel.extend({
+ idAttribute: 'property_id'
+ });
+ var View = Backbone.RelationalModel.extend({
+ idAttribute: 'id',
+
+ relations: [{
+ type: Backbone.HasMany,
+ key: 'properties',
+ keyDestination: 'properties_attributes',
+ relatedModel: Property,
+ reverseRelation: {
+ key: 'view',
+ keyDestination: 'view_attributes',
+ includeInJSON: true
+ }
+ }]
+ });
+
+ var property1 = new Property({
+ property_id: 1,
+ key: 'width',
+ value: 500,
+ view: 5
+ });
+
+ var view = new View({
+ id: 5,
+ properties: [ 2 ]
+ });
+
+ var property2 = new Property({
+ property_id: 2,
+ key: 'height',
+ value: 400
+ });
var viewJSON = view.toJSON();
- ok( viewJSON.property_ids && viewJSON.property_ids.length === 2, "'viewJSON' has two 'property_ids'" );
+ ok( viewJSON.properties_attributes && viewJSON.properties_attributes.length === 2, "'viewJSON' has two 'properties_attributes'" );
ok( typeof viewJSON.properties === 'undefined', "'viewJSON' does not have 'properties'" );
-
- console.log( view, viewJSON, property1, property2 );
+ });
+
+ test( "'collectionOptionsCallback' sets the options on the created HasMany Collections", function() {
+ var zoo = new Zoo();
+ ok( zoo.get("animals").url === "zoo/" + zoo.cid + "/animal/");
});
test( "Uses 'modelBuilder' function to build models for HasOne relations", function() {
@@ -1278,7 +1326,7 @@ $(document).ready(function() {
module( "Backbone.HasMany", { setup: initObjects } );
-
+
test( "Listeners on 'add'/'remove'", function() {
expect( 7 );

0 comments on commit c30c3de

Please sign in to comment.