backbone-relational-rails 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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