backbone-relational-rails 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -18,7 +18,7 @@ Add the following directive to your Javascript manifest file (application.js):
18
18
 
19
19
  ## Versioning
20
20
 
21
- backbone-relational-rails 0.6.0 == Backbone-relational 0.6.0
21
+ backbone-relational-rails 0.6.1 == Backbone-relational 0.6.1
22
22
 
23
23
  Every attempt is made to mirror the currently shipping Backbone-relational version number wherever possible.
24
24
  The major, minor, and patch version numbers will always represent the Backbone-relational version. Should a gem
@@ -1,7 +1,7 @@
1
1
  module Backbone
2
2
  module Relational
3
3
  module Rails
4
- VERSION = "0.6.0"
4
+ VERSION = "0.6.1"
5
5
  end
6
6
  end
7
7
  end
@@ -148,8 +148,8 @@
148
148
  * @param {Backbone.RelationalModel} modelType
149
149
  */
150
150
  setupSuperModel: function( modelType ) {
151
- _.find( this._subModels, function( subModelDef ) {
152
- return _.find( subModelDef.subModels, function( subModelTypeName, typeValue ) {
151
+ _.find( this._subModels || [], function( subModelDef ) {
152
+ return _.find( subModelDef.subModels || [], function( subModelTypeName, typeValue ) {
153
153
  var subModelType = this.getObjectByName( subModelTypeName );
154
154
 
155
155
  if ( modelType === subModelType ) {
@@ -176,8 +176,8 @@
176
176
  * @param {String|Object} relation.relatedModel
177
177
  */
178
178
  addReverseRelation: function( relation ) {
179
- var exists = _.any( this._reverseRelations, function( rel ) {
180
- return _.all( relation, function( val, key ) {
179
+ var exists = _.any( this._reverseRelations || [], function( rel ) {
180
+ return _.all( relation || [], function( val, key ) {
181
181
  return val === rel[ key ];
182
182
  });
183
183
  });
@@ -191,7 +191,7 @@
191
191
  }
192
192
  model.prototype.relations.push( relation );
193
193
 
194
- _.each( model._subModels, function( subModel ) {
194
+ _.each( model._subModels || [], function( subModel ) {
195
195
  addRelation( subModel, relation );
196
196
  }, this );
197
197
  };
@@ -252,9 +252,9 @@
252
252
  var parts = name.split( '.' ),
253
253
  type = null;
254
254
 
255
- _.find( this._modelScopes, function( scope ) {
256
- type = _.reduce( parts, function( memo, val ) {
257
- return memo[ val ];
255
+ _.find( this._modelScopes || [], function( scope ) {
256
+ type = _.reduce( parts || [], function( memo, val ) {
257
+ return memo ? memo[ val ] : undefined;
258
258
  }, scope );
259
259
 
260
260
  if ( type && type !== scope ) {
@@ -337,11 +337,18 @@
337
337
  * @param {Backbone.RelationalModel} model
338
338
  */
339
339
  register: function( model ) {
340
- var modelColl = model.collection;
341
340
  var coll = this.getCollection( model );
342
- coll && coll.add( model );
343
- model.bind( 'destroy', this.unregister, this );
344
- model.collection = modelColl;
341
+
342
+ if ( coll ) {
343
+ if ( coll.get( model ) ) {
344
+ throw new Error( "Cannot instantiate more than one Backbone.RelationalModel with the same id per type!" );
345
+ }
346
+
347
+ var modelColl = model.collection;
348
+ coll.add( model );
349
+ model.bind( 'destroy', this.unregister, this );
350
+ model.collection = modelColl;
351
+ }
345
352
  },
346
353
 
347
354
  /**
@@ -404,10 +411,15 @@
404
411
  }
405
412
 
406
413
  if ( instance ) {
407
- this.keyContents = this.instance.get( this.keySource );
414
+ var contentKey = this.keySource;
415
+ if ( contentKey !== this.key && typeof this.instance.get( this.key ) === 'object' ) {
416
+ contentKey = this.key;
417
+ }
418
+
419
+ this.keyContents = this.instance.get( contentKey );
408
420
 
409
421
  // Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'.
410
- if ( this.key !== this.keySource ) {
422
+ if ( this.keySource !== this.key ) {
411
423
  this.instance.unset( this.keySource, { silent: true } );
412
424
  }
413
425
 
@@ -511,7 +523,7 @@
511
523
 
512
524
  // Check if we're not attempting to create a duplicate relationship
513
525
  if ( i && i._relations.length ) {
514
- var exists = _.any( i._relations, function( rel ) {
526
+ var exists = _.any( i._relations || [], function( rel ) {
515
527
  var hasReverseRelation = this.reverseRelation.key && rel.reverseRelation.key;
516
528
  return rel.relatedModel === rm && rel.key === k &&
517
529
  ( !hasReverseRelation || this.reverseRelation.key === rel.reverseRelation.key );
@@ -564,8 +576,8 @@
564
576
  var reverseRelations = [];
565
577
  // Iterate over 'model', 'this.related.models' (if this.related is a Backbone.Collection), or wrap 'this.related' in an array.
566
578
  var models = !_.isUndefined( model ) ? [ model ] : this.related && ( this.related.models || [ this.related ] );
567
- _.each( models , function( related ) {
568
- _.each( related.getRelations(), function( relation ) {
579
+ _.each( models || [], function( related ) {
580
+ _.each( related.getRelations() || [], function( relation ) {
569
581
  if ( this._isReverseRelation( relation ) ) {
570
582
  reverseRelations.push( relation );
571
583
  }
@@ -614,7 +626,7 @@
614
626
  .unbind( 'relational:add', this._relatedModelAdded )
615
627
  .unbind( 'relational:remove', this._relatedModelRemoved );
616
628
 
617
- _.each( this.getReverseRelations(), function( relation ) {
629
+ _.each( this.getReverseRelations() || [], function( relation ) {
618
630
  relation.removeRelated( this.instance );
619
631
  }, this );
620
632
  }
@@ -634,7 +646,7 @@
634
646
  this.setRelated( model );
635
647
 
636
648
  // Notify new 'related' object of the new relation.
637
- _.each( this.getReverseRelations(), function( relation ) {
649
+ _.each( this.getReverseRelations() || [], function( relation ) {
638
650
  relation.addRelated( this.instance );
639
651
  }, this );
640
652
  },
@@ -688,7 +700,7 @@
688
700
 
689
701
  // Notify old 'related' object of the terminated relation
690
702
  if ( oldRelated && this.related !== oldRelated ) {
691
- _.each( this.getReverseRelations( oldRelated ), function( relation ) {
703
+ _.each( this.getReverseRelations( oldRelated ) || [], function( relation ) {
692
704
  relation.removeRelated( this.instance, options );
693
705
  }, this );
694
706
  }
@@ -696,7 +708,7 @@
696
708
  // Notify new 'related' object of the new relation. Note we do re-apply even if this.related is oldRelated;
697
709
  // that can be necessary for bi-directional relations if 'this.instance' was created after 'this.related'.
698
710
  // In that case, 'this.instance' will already know 'this.related', but the reverse might not exist yet.
699
- _.each( this.getReverseRelations(), function( relation ) {
711
+ _.each( this.getReverseRelations() || [], function( relation ) {
700
712
  relation.addRelated( this.instance, options );
701
713
  }, this);
702
714
 
@@ -841,7 +853,7 @@
841
853
  this.keyContents = _.isArray( this.keyContents ) ? this.keyContents : [ this.keyContents ];
842
854
 
843
855
  // Try to find instances of the appropriate 'relatedModel' in the store
844
- _.each( this.keyContents, function( item ) {
856
+ _.each( this.keyContents || [], function( item ) {
845
857
  var model = null;
846
858
  if ( item instanceof this.relatedModel ) {
847
859
  model = item;
@@ -871,11 +883,6 @@
871
883
  options = this.sanitizeOptions( options );
872
884
  this.keyContents = attr;
873
885
 
874
- // Notify old 'related' object of the terminated relation
875
- _.each( this.getReverseRelations(), function( relation ) {
876
- relation.removeRelated( this.instance, options );
877
- }, this );
878
-
879
886
  // Replace 'this.related' by 'attr' if it is a Backbone.Collection
880
887
  if ( attr instanceof Backbone.Collection ) {
881
888
  this._prepareCollection( attr );
@@ -885,25 +892,42 @@
885
892
  // Re-use the current 'this.related' if it is a Backbone.Collection, and remove any current entries.
886
893
  // Otherwise, create a new collection.
887
894
  else {
888
- var coll;
895
+ var oldIds = {}, newIds = {};
889
896
 
890
- if ( this.related instanceof Backbone.Collection ) {
891
- coll = this.related;
892
- coll.remove( coll.models );
897
+ if (!_.isArray( attr ) && attr !== undefined) {
898
+ attr = [ attr ];
893
899
  }
894
- else {
900
+ var oldIds;
901
+ _.each( attr, function( attributes ) {
902
+ newIds[ attributes.id ] = true;
903
+ });
904
+
905
+ var coll = this.related;
906
+ if ( coll instanceof Backbone.Collection ) {
907
+ // Make sure to operate on a copy since we're removing while iterating
908
+ _.each( coll.models.slice(0) , function( model ) {
909
+ // When fetch is called with the 'keepNewModels' option, we don't want to remove
910
+ // client-created new models when the fetch is completed.
911
+ if ( !options.keepNewModels || !model.isNew() ) {
912
+ oldIds[ model.id ] = true;
913
+ coll.remove( model, { silent: (model.id in newIds) } );
914
+ }
915
+ });
916
+ } else {
895
917
  coll = this._prepareCollection();
896
918
  }
897
919
 
920
+ _.each( attr, function( attributes ) {
921
+ var model = this.relatedModel.findOrCreate( attributes, { create: this.options.createModels } );
922
+ if (model) {
923
+ coll.add( model, { silent: (attributes.id in oldIds)} );
924
+ }
925
+ }, this);
926
+
898
927
  this.setRelated( coll );
899
- this.findRelated( options );
928
+
900
929
  }
901
930
 
902
- // Notify new 'related' object of the new relation
903
- _.each( this.getReverseRelations(), function( relation ) {
904
- relation.addRelated( this.instance, options );
905
- }, this );
906
-
907
931
  var dit = this;
908
932
  Backbone.Relational.eventQueue.add( function() {
909
933
  !options.silentChange && dit.instance.trigger( 'update:' + dit.key, dit.instance, dit.related, options );
@@ -914,7 +938,7 @@
914
938
  options = this.sanitizeOptions( options );
915
939
  if ( !this.related.getByCid( model ) && !this.related.get( model ) ) {
916
940
  // Check if this new model was specified in 'this.keyContents'
917
- var item = _.any( this.keyContents, function( item ) {
941
+ var item = _.any( this.keyContents || [], function( item ) {
918
942
  var id = Backbone.Relational.store.resolveIdForItem( this.relatedModel, item );
919
943
  return !_.isNull( id ) && id === model.id;
920
944
  }, this );
@@ -939,7 +963,7 @@
939
963
 
940
964
  options = this.sanitizeOptions( options );
941
965
 
942
- _.each( this.getReverseRelations( model ), function( relation ) {
966
+ _.each( this.getReverseRelations( model ) || [], function( relation ) {
943
967
  relation.addRelated( this.instance, options );
944
968
  }, this );
945
969
 
@@ -962,7 +986,7 @@
962
986
 
963
987
  options = this.sanitizeOptions( options );
964
988
 
965
- _.each( this.getReverseRelations( model ), function( relation ) {
989
+ _.each( this.getReverseRelations( model ) || [], function( relation ) {
966
990
  relation.removeRelated( this.instance, options );
967
991
  }, this );
968
992
 
@@ -1078,7 +1102,7 @@
1078
1102
  this.acquire(); // Setting up relations often also involve calls to 'set', and we only want to enter this function once
1079
1103
  this._relations = [];
1080
1104
 
1081
- _.each( this.relations, function( rel ) {
1105
+ _.each( this.relations || [], function( rel ) {
1082
1106
  var type = !_.isString( rel.type ) ? rel.type : Backbone[ rel.type ] || Backbone.Relational.store.getObjectByName( rel.type );
1083
1107
  if ( type && type.prototype instanceof Backbone.Relation ) {
1084
1108
  new type( this, rel ); // Also pushes the new Relation into _relations
@@ -1099,7 +1123,7 @@
1099
1123
  */
1100
1124
  updateRelations: function( options ) {
1101
1125
  if ( this._isInitialized && !this.isLocked() ) {
1102
- _.each( this._relations, function( rel ) {
1126
+ _.each( this._relations || [], function( rel ) {
1103
1127
  // Update from data in `rel.keySource` if set, or `rel.key` otherwise
1104
1128
  var val = this.attributes[ rel.keySource ] || this.attributes[ rel.key ];
1105
1129
  if ( rel.related !== val ) {
@@ -1149,8 +1173,8 @@
1149
1173
  /**
1150
1174
  * Retrieve related objects.
1151
1175
  * @param key {string} The relation key to fetch models for.
1152
- * @param options {Object} Options for 'Backbone.Model.fetch' and 'Backbone.sync'.
1153
- * @param update {boolean} Whether to force a fetch from the server (updating existing models).
1176
+ * @param [options] {Object} Options for 'Backbone.Model.fetch' and 'Backbone.sync'.
1177
+ * @param [update=false] {boolean} Whether to force a fetch from the server (updating existing models).
1154
1178
  * @return {jQuery.when[]} An array of request objects
1155
1179
  */
1156
1180
  fetchRelated: function( key, options, update ) {
@@ -1170,12 +1194,12 @@
1170
1194
  var model;
1171
1195
 
1172
1196
  if ( _.isObject( item ) ) {
1173
- model = rel.relatedModel.build( item );
1197
+ model = rel.relatedModel.findOrCreate( item );
1174
1198
  }
1175
1199
  else {
1176
1200
  var attrs = {};
1177
1201
  attrs[ rel.relatedModel.prototype.idAttribute ] = item;
1178
- model = rel.relatedModel.build( attrs );
1202
+ model = rel.relatedModel.findOrCreate( attrs );
1179
1203
  }
1180
1204
 
1181
1205
  return model;
@@ -1194,7 +1218,7 @@
1194
1218
  {
1195
1219
  error: function() {
1196
1220
  var args = arguments;
1197
- _.each( models, function( model ) {
1221
+ _.each( models || [], function( model ) {
1198
1222
  model.trigger( 'destroy', model, model.collection, options );
1199
1223
  options.error && options.error.apply( model, args );
1200
1224
  });
@@ -1208,7 +1232,7 @@
1208
1232
  requests = [ rel.related.fetch( opts ) ];
1209
1233
  }
1210
1234
  else {
1211
- requests = _.map( models, function( model ) {
1235
+ requests = _.map( models || [], function( model ) {
1212
1236
  var opts = _.defaults(
1213
1237
  {
1214
1238
  error: function() {
@@ -1306,7 +1330,7 @@
1306
1330
  attributes[ this.idAttribute ] = null;
1307
1331
  }
1308
1332
 
1309
- _.each( this.getRelations(), function( rel ) {
1333
+ _.each( this.getRelations() || [], function( rel ) {
1310
1334
  delete attributes[ rel.key ];
1311
1335
  });
1312
1336
 
@@ -1316,25 +1340,25 @@
1316
1340
  /**
1317
1341
  * Convert relations to JSON, omits them when required
1318
1342
  */
1319
- toJSON: function() {
1343
+ toJSON: function(options) {
1320
1344
  // If this Model has already been fully serialized in this branch once, return to avoid loops
1321
1345
  if ( this.isLocked() ) {
1322
1346
  return this.id;
1323
1347
  }
1324
1348
 
1325
1349
  this.acquire();
1326
- var json = Backbone.Model.prototype.toJSON.call( this );
1350
+ var json = Backbone.Model.prototype.toJSON.call( this, options );
1327
1351
 
1328
1352
  if ( this.constructor._superModel && !( this.constructor._subModelTypeAttribute in json ) ) {
1329
1353
  json[ this.constructor._subModelTypeAttribute ] = this.constructor._subModelTypeValue;
1330
1354
  }
1331
1355
 
1332
- _.each( this._relations, function( rel ) {
1356
+ _.each( this._relations || [], function( rel ) {
1333
1357
  var value = json[ rel.key ];
1334
1358
 
1335
1359
  if ( rel.options.includeInJSON === true) {
1336
1360
  if ( value && _.isFunction( value.toJSON ) ) {
1337
- json[ rel.keyDestination ] = value.toJSON();
1361
+ json[ rel.keyDestination ] = value.toJSON( options );
1338
1362
  }
1339
1363
  else {
1340
1364
  json[ rel.keyDestination ] = null;
@@ -1406,7 +1430,7 @@
1406
1430
  }
1407
1431
 
1408
1432
  // Initialize all reverseRelations that belong to this new model.
1409
- _.each( this.prototype.relations, function( rel ) {
1433
+ _.each( this.prototype.relations || [], function( rel ) {
1410
1434
  if ( !rel.model ) {
1411
1435
  rel.model = this;
1412
1436
  }
@@ -1471,7 +1495,7 @@
1471
1495
  if ( this._superModel ) {
1472
1496
  //
1473
1497
  if ( this._superModel.prototype.relations ) {
1474
- var supermodelRelationsExist = _.any( this.prototype.relations, function( rel ) {
1498
+ var supermodelRelationsExist = _.any( this.prototype.relations || [], function( rel ) {
1475
1499
  return rel.model && rel.model !== this;
1476
1500
  }, this );
1477
1501
 
@@ -1487,7 +1511,7 @@
1487
1511
 
1488
1512
  // If we came here through 'build' for a model that has 'subModelTypes', and not all of them have been resolved yet, try to resolve each.
1489
1513
  if ( this.prototype.subModelTypes && _.keys( this.prototype.subModelTypes ).length !== _.keys( this._subModels ).length ) {
1490
- _.each( this.prototype.subModelTypes, function( subModelTypeName ) {
1514
+ _.each( this.prototype.subModelTypes || [], function( subModelTypeName ) {
1491
1515
  var subModelType = Backbone.Relational.store.getObjectByName( subModelTypeName );
1492
1516
  subModelType && subModelType.initializeModelHierarchy();
1493
1517
  });
@@ -1505,14 +1529,15 @@
1505
1529
  * @return {Backbone.RelationalModel}
1506
1530
  */
1507
1531
  findOrCreate: function( attributes, options ) {
1532
+ var parsedAttributes = (_.isObject( attributes ) && this.prototype.parse) ? this.prototype.parse( attributes ) : attributes;
1508
1533
  // Try to find an instance of 'this' model type in the store
1509
- var model = Backbone.Relational.store.find( this, attributes );
1534
+ var model = Backbone.Relational.store.find( this, parsedAttributes );
1510
1535
 
1511
1536
  // If we found an instance, update it with the data in 'item'; if not, create an instance
1512
1537
  // (unless 'options.create' is false).
1513
1538
  if ( _.isObject( attributes ) ) {
1514
1539
  if ( model ) {
1515
- model.set( attributes, options );
1540
+ model.set( parsedAttributes, options );
1516
1541
  }
1517
1542
  else if ( !options || ( options && options.create !== false ) ) {
1518
1543
  model = this.build( attributes, options );
@@ -1534,9 +1559,9 @@
1534
1559
  if ( !( model instanceof Backbone.Model ) ) {
1535
1560
  var attrs = model;
1536
1561
  options.collection = this;
1537
-
1538
- if ( typeof this.model.build !== 'undefined' ) {
1539
- model = this.model.build( attrs, options );
1562
+
1563
+ if ( typeof this.model.findOrCreate !== 'undefined' ) {
1564
+ model = this.model.findOrCreate( attrs, options );
1540
1565
  }
1541
1566
  else {
1542
1567
  model = new this.model( attrs, options );
@@ -1567,32 +1592,25 @@
1567
1592
  var modelsToAdd = [];
1568
1593
 
1569
1594
  //console.debug( 'calling add on coll=%o; model=%o, options=%o', this, models, options );
1570
- _.each( models, function( model ) {
1571
- if ( !( model instanceof Backbone.Model ) ) {
1572
- // Try to find 'model' in Backbone.store. If it already exists, set the new properties on it.
1573
- var existingModel = Backbone.Relational.store.find( this.model, model[ this.model.prototype.idAttribute ] );
1574
- if ( existingModel ) {
1575
- existingModel.set( existingModel.parse ? existingModel.parse( model ) : model, options );
1576
- model = existingModel;
1577
- }
1578
- else {
1579
- model = Backbone.Collection.prototype._prepareModel.call( this, model, options );
1580
- }
1581
- }
1582
-
1583
- if ( model instanceof Backbone.Model && !this.get( model ) && !this.getByCid( model ) ) {
1584
- modelsToAdd.push( model );
1585
- }
1586
- }, this );
1595
+ _.each( models || [], function( model ) {
1596
+ if ( !( model instanceof Backbone.Model ) ) {
1597
+ // `_prepareModel` attempts to find `model` in Backbone.store through `findOrCreate`,
1598
+ // and sets the new properties on it if is found. Otherwise, a new model is instantiated.
1599
+ model = Backbone.Collection.prototype._prepareModel.call( this, model, options );
1600
+ }
1587
1601
 
1602
+ if ( model instanceof Backbone.Model && !this.get( model ) && !this.getByCid( model ) ) {
1603
+ modelsToAdd.push( model );
1604
+ }
1605
+ }, this );
1588
1606
 
1589
1607
  // Add 'models' in a single batch, so the original add will only be called once (and thus 'sort', etc).
1590
1608
  if ( modelsToAdd.length ) {
1591
1609
  add.call( this, modelsToAdd, options );
1592
1610
 
1593
- _.each( modelsToAdd, function( model ) {
1594
- this.trigger( 'relational:add', model, this, options );
1595
- }, this );
1611
+ _.each( modelsToAdd || [], function( model ) {
1612
+ this.trigger( 'relational:add', model, this, options );
1613
+ }, this );
1596
1614
  }
1597
1615
 
1598
1616
  return this;
@@ -1612,7 +1630,7 @@
1612
1630
  }
1613
1631
 
1614
1632
  //console.debug('calling remove on coll=%o; models=%o, options=%o', this, models, options );
1615
- _.each( models, function( model ) {
1633
+ _.each( models || [], function( model ) {
1616
1634
  model = this.getByCid( model ) || this.get( model );
1617
1635
 
1618
1636
  if ( model instanceof Backbone.Model ) {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backbone-relational-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -56,7 +56,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
56
56
  version: '0'
57
57
  segments:
58
58
  - 0
59
- hash: 2242082046468411203
59
+ hash: -4585206542888784471
60
60
  required_rubygems_version: !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
@@ -65,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
65
  version: '0'
66
66
  segments:
67
67
  - 0
68
- hash: 2242082046468411203
68
+ hash: -4585206542888784471
69
69
  requirements: []
70
70
  rubyforge_project:
71
71
  rubygems_version: 1.8.24