backbone-relational-rails 0.8.0 → 0.8.5

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.8.0 == Backbone-relational 0.8.0
21
+ backbone-relational-rails 0.8.5 == Backbone-relational 0.8.5
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
@@ -26,7 +26,7 @@ bug be discovered, a 4th version identifier will be added and incremented.
26
26
 
27
27
  ## Backbone Version
28
28
 
29
- This release of Backbone-relational is only compatible with Backbone >= 0.9.10.
29
+ This release of Backbone-relational is only compatible with Backbone >= 1.0.0.
30
30
 
31
31
  ## Acknowledgements
32
32
 
@@ -1,7 +1,7 @@
1
1
  module Backbone
2
2
  module Relational
3
3
  module Rails
4
- VERSION = "0.8.0"
4
+ VERSION = "0.8.5"
5
5
  end
6
6
  end
7
7
  end
@@ -1,6 +1,6 @@
1
1
  /* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab: */
2
2
  /**
3
- * Backbone-relational.js 0.8.0
3
+ * Backbone-relational.js 0.8.5
4
4
  * (c) 2011-2013 Paul Uithol and contributors (https://github.com/PaulUithol/Backbone-relational/graphs/contributors)
5
5
  *
6
6
  * Backbone-relational may be freely distributed under the MIT license; see the accompanying LICENSE.txt.
@@ -149,6 +149,14 @@
149
149
  this._modelScopes.push( scope );
150
150
  },
151
151
 
152
+ /**
153
+ * Remove a scope.
154
+ * @param {Object} scope
155
+ */
156
+ removeModelScope: function( scope ) {
157
+ this._modelScopes = _.without( this._modelScopes, scope );
158
+ },
159
+
152
160
  /**
153
161
  * Add a set of subModelTypes to the store, that can be used to resolve the '_superModel'
154
162
  * for a model later in 'setupSuperModel'.
@@ -367,7 +375,7 @@
367
375
  },
368
376
 
369
377
  /**
370
- *
378
+ * Find a specific model of a certain `type` in the store
371
379
  * @param type
372
380
  * @param {String|Number|Object|Backbone.RelationalModel} item
373
381
  */
@@ -396,13 +404,6 @@
396
404
  var coll = this.getCollection( model );
397
405
 
398
406
  if ( coll ) {
399
- if ( coll.get( model ) ) {
400
- if ( Backbone.Relational.showWarnings && typeof console !== 'undefined' ) {
401
- console.warn( 'Duplicate id! Old RelationalModel=%o, new RelationalModel=%o', coll.get( model ), model );
402
- }
403
- throw new Error( "Cannot instantiate more than one Backbone.RelationalModel with the same id per type!" );
404
- }
405
-
406
407
  var modelColl = model.collection;
407
408
  coll.add( model );
408
409
  this.listenTo( model, 'destroy', this.unregister, this );
@@ -410,13 +411,35 @@
410
411
  }
411
412
  },
412
413
 
414
+ /**
415
+ * Check if the given model may use the given `id`
416
+ * @param model
417
+ * @param [id]
418
+ */
419
+ checkId: function( model, id ) {
420
+ var coll = this.getCollection( model ),
421
+ duplicate = coll && coll.get( id );
422
+
423
+ if ( duplicate && model !== duplicate ) {
424
+ if ( Backbone.Relational.showWarnings && typeof console !== 'undefined' ) {
425
+ console.warn( 'Duplicate id! Old RelationalModel=%o, new RelationalModel=%o', duplicate, model );
426
+ }
427
+
428
+ throw new Error( "Cannot instantiate more than one Backbone.RelationalModel with the same id per type!" );
429
+ }
430
+ },
431
+
413
432
  /**
414
433
  * Explicitly update a model's id in its store collection
415
434
  * @param {Backbone.RelationalModel} model
416
- */
435
+ */
417
436
  update: function( model ) {
418
437
  var coll = this.getCollection( model );
438
+ // This triggers updating the lookup indices kept in a collection
419
439
  coll._onModelEvent( 'change:' + model.idAttribute, model, coll );
440
+
441
+ // Trigger an event on model so related models (having the model's new id in their keyContents) can add it.
442
+ model.trigger( 'relational:change:id', model, coll );
420
443
  },
421
444
 
422
445
  /**
@@ -519,7 +542,7 @@
519
542
 
520
543
  // When 'relatedModel' are created or destroyed, check if it affects this relation.
521
544
  this.listenTo( this.instance, 'destroy', this.destroy )
522
- .listenTo( this.relatedCollection, 'relational:add', this.tryAddRelated )
545
+ .listenTo( this.relatedCollection, 'relational:add relational:change:id', this.tryAddRelated )
523
546
  .listenTo( this.relatedCollection, 'relational:remove', this.removeRelated )
524
547
  }
525
548
  };
@@ -688,6 +711,11 @@
688
711
  related = this.relatedModel.findOrCreate( this.keyContents, opts );
689
712
  }
690
713
 
714
+ // Nullify `keyId` if we have a related model; in case it was already part of the relation
715
+ if ( this.related ) {
716
+ this.keyId = null;
717
+ }
718
+
691
719
  return related;
692
720
  },
693
721
 
@@ -804,7 +832,7 @@
804
832
  if ( _.isString( this.collectionType ) ) {
805
833
  this.collectionType = Backbone.Relational.store.getObjectByName( this.collectionType );
806
834
  }
807
- if ( !this.collectionType.prototype instanceof Backbone.Collection ){
835
+ if ( this.collectionType !== Backbone.Collection && !( this.collectionType.prototype instanceof Backbone.Collection ) ) {
808
836
  throw new Error( '`collectionType` must inherit from Backbone.Collection' );
809
837
  }
810
838
 
@@ -878,7 +906,9 @@
878
906
  }
879
907
  else {
880
908
  // If `merge` is true, update models here, instead of during update.
881
- model = this.relatedModel.findOrCreate( attributes, _.extend( { merge: true }, options, { create: this.options.createModels } ) );
909
+ model = this.relatedModel.findOrCreate( attributes,
910
+ _.extend( { merge: true }, options, { create: this.options.createModels } )
911
+ );
882
912
  }
883
913
 
884
914
  model && toAdd.push( model );
@@ -891,9 +921,14 @@
891
921
  related = this._prepareCollection();
892
922
  }
893
923
 
894
- related.update( toAdd, _.defaults( { merge: false, parse: false }, options ) );
924
+ // By now, both `merge` and `parse` will already have been executed for models if they were specified.
925
+ // Disable them to prevent additional calls.
926
+ related.set( toAdd, _.defaults( { merge: false, parse: false }, options ) );
895
927
  }
896
928
 
929
+ // Remove entries from `keyIds` that were already part of the relation (and are thus 'unchanged')
930
+ this.keyIds = _.difference( this.keyIds, _.pluck( related.models, 'id' ) );
931
+
897
932
  return related;
898
933
  },
899
934
 
@@ -1004,7 +1039,7 @@
1004
1039
  var dit = this;
1005
1040
  model.queue( function() {
1006
1041
  if ( dit.related && !dit.related.get( model ) ) {
1007
- dit.related.add( model, options );
1042
+ dit.related.add( model, _.defaults( { parse: false }, options ) );
1008
1043
  }
1009
1044
  });
1010
1045
  },
@@ -1037,12 +1072,12 @@
1037
1072
  // Nasty hack, for cases like 'model.get( <HasMany key> ).add( item )'.
1038
1073
  // Defer 'processQueue', so that when 'Relation.createModels' is used we trigger 'HasMany'
1039
1074
  // collection events only after the model is really fully set up.
1040
- // Example: "p.get('jobs').add( { company: c, person: p } )".
1075
+ // Example: event for "p.on( 'add:jobs' )" -> "p.get('jobs').add( { company: c.id, person: p.id } )".
1041
1076
  if ( options && options.collection ) {
1042
1077
  var dit = this,
1043
1078
  collection = this.collection = options.collection;
1044
1079
 
1045
- // Prevent this option from cascading down to related models; they shouldn't go into this `if` clause.
1080
+ // Prevent `collection` from cascading down to nested models; they shouldn't go into this `if` clause.
1046
1081
  delete options.collection;
1047
1082
 
1048
1083
  this._deferProcessing = true;
@@ -1125,7 +1160,7 @@
1125
1160
  else {
1126
1161
  Backbone.Model.prototype.trigger.apply( this, arguments );
1127
1162
  }
1128
-
1163
+
1129
1164
  return this;
1130
1165
  },
1131
1166
 
@@ -1136,11 +1171,11 @@
1136
1171
  initializeRelations: function( options ) {
1137
1172
  this.acquire(); // Setting up relations often also involve calls to 'set', and we only want to enter this function once
1138
1173
  this._relations = {};
1139
-
1174
+
1140
1175
  _.each( this.relations || [], function( rel ) {
1141
1176
  Backbone.Relational.store.initializeRelation( this, rel, options );
1142
1177
  }, this );
1143
-
1178
+
1144
1179
  this._isInitialized = true;
1145
1180
  this.release();
1146
1181
  this.processQueue();
@@ -1209,15 +1244,22 @@
1209
1244
  var setUrl,
1210
1245
  requests = [],
1211
1246
  rel = this.getRelation( key ),
1212
- keys = rel && ( rel.keyIds || [ rel.keyId ] ),
1213
- toFetch = keys && _.select( keys || [], function( id ) {
1214
- return ( id || id === 0 ) && ( refresh || !Backbone.Relational.store.find( rel.relatedModel, id ) );
1215
- }, this );
1216
-
1217
- if ( toFetch && toFetch.length ) {
1247
+ idsToFetch = rel && ( rel.keyIds || ( ( rel.keyId || rel.keyId === 0 ) ? [ rel.keyId ] : [] ) );
1248
+
1249
+ // On `refresh`, add the ids for current models in the relation to `idsToFetch`
1250
+ if ( refresh ) {
1251
+ var models = rel.related instanceof Backbone.Collection ? rel.related.models : [ rel.related ];
1252
+ _.each( models, function( model ) {
1253
+ if ( model.id || model.id === 0 ) {
1254
+ idsToFetch.push( model.id );
1255
+ }
1256
+ });
1257
+ }
1258
+
1259
+ if ( idsToFetch && idsToFetch.length ) {
1218
1260
  // Find (or create) a model for each one that is to be fetched
1219
1261
  var created = [],
1220
- models = _.map( toFetch, function( id ) {
1262
+ models = _.map( idsToFetch, function( id ) {
1221
1263
  var model = Backbone.Relational.store.find( rel.relatedModel, id );
1222
1264
 
1223
1265
  if ( !model ) {
@@ -1234,7 +1276,7 @@
1234
1276
  if ( rel.related instanceof Backbone.Collection && _.isFunction( rel.related.url ) ) {
1235
1277
  setUrl = rel.related.url( models );
1236
1278
  }
1237
-
1279
+
1238
1280
  // An assumption is that when 'Backbone.Collection.url' is a function, it can handle building of set urls.
1239
1281
  // To make sure it can, test if the url we got by supplying a list of models to fetch is different from
1240
1282
  // the one supplied for the default fetch action (without args to 'url').
@@ -1303,7 +1345,7 @@
1303
1345
 
1304
1346
  set: function( key, value, options ) {
1305
1347
  Backbone.Relational.eventQueue.block();
1306
-
1348
+
1307
1349
  // Duplicate backbone's behavior to allow separate key/value parameters, instead of a single 'attributes' object
1308
1350
  var attributes;
1309
1351
  if ( _.isObject( key ) || key == null ) {
@@ -1314,20 +1356,24 @@
1314
1356
  attributes = {};
1315
1357
  attributes[ key ] = value;
1316
1358
  }
1317
-
1318
- var result = Backbone.Model.prototype.set.apply( this, arguments );
1319
-
1320
- // Ideal place to set up relations :)
1359
+
1321
1360
  try {
1361
+ var id = this.id,
1362
+ newId = attributes && this.idAttribute in attributes && attributes[ this.idAttribute ];
1363
+
1364
+ // Check if we're not setting a duplicate id before actually calling `set`.
1365
+ Backbone.Relational.store.checkId( this, newId );
1366
+
1367
+ var result = Backbone.Model.prototype.set.apply( this, arguments );
1368
+
1369
+ // Ideal place to set up relations, if this is the first time we're here for this model
1322
1370
  if ( !this._isInitialized && !this.isLocked() ) {
1323
1371
  this.constructor.initializeModelHierarchy();
1324
-
1325
1372
  Backbone.Relational.store.register( this );
1326
-
1327
1373
  this.initializeRelations( options );
1328
1374
  }
1329
- // Update the 'idAttribute' in Backbone.store if; we don't want it to miss an 'id' update due to {silent:true}
1330
- else if ( attributes && this.idAttribute in attributes ) {
1375
+ // The store should know about an `id` update asap
1376
+ else if ( newId && newId !== id ) {
1331
1377
  Backbone.Relational.store.update( this );
1332
1378
  }
1333
1379
 
@@ -1345,13 +1391,13 @@
1345
1391
 
1346
1392
  unset: function( attribute, options ) {
1347
1393
  Backbone.Relational.eventQueue.block();
1348
-
1394
+
1349
1395
  var result = Backbone.Model.prototype.unset.apply( this, arguments );
1350
1396
  this.updateRelations( options );
1351
-
1397
+
1352
1398
  // Try to run the global queue holding external events
1353
1399
  Backbone.Relational.eventQueue.unblock();
1354
-
1400
+
1355
1401
  return result;
1356
1402
  },
1357
1403
 
@@ -1360,10 +1406,10 @@
1360
1406
 
1361
1407
  var result = Backbone.Model.prototype.clear.apply( this, arguments );
1362
1408
  this.updateRelations( options );
1363
-
1409
+
1364
1410
  // Try to run the global queue holding external events
1365
1411
  Backbone.Relational.eventQueue.unblock();
1366
-
1412
+
1367
1413
  return result;
1368
1414
  },
1369
1415
 
@@ -1388,63 +1434,68 @@
1388
1434
  if ( this.isLocked() ) {
1389
1435
  return this.id;
1390
1436
  }
1391
-
1437
+
1392
1438
  this.acquire();
1393
1439
  var json = Backbone.Model.prototype.toJSON.call( this, options );
1394
-
1440
+
1395
1441
  if ( this.constructor._superModel && !( this.constructor._subModelTypeAttribute in json ) ) {
1396
1442
  json[ this.constructor._subModelTypeAttribute ] = this.constructor._subModelTypeValue;
1397
1443
  }
1398
-
1444
+
1399
1445
  _.each( this._relations, function( rel ) {
1400
- var value = json[ rel.key ];
1446
+ var related = json[ rel.key ],
1447
+ includeInJSON = rel.options.includeInJSON,
1448
+ value = null;
1401
1449
 
1402
- if ( rel.options.includeInJSON === true) {
1403
- if ( value && _.isFunction( value.toJSON ) ) {
1404
- json[ rel.keyDestination ] = value.toJSON( options );
1405
- }
1406
- else {
1407
- json[ rel.keyDestination ] = null;
1450
+ if ( includeInJSON === true ) {
1451
+ if ( related && _.isFunction( related.toJSON ) ) {
1452
+ value = related.toJSON( options );
1408
1453
  }
1409
1454
  }
1410
- else if ( _.isString( rel.options.includeInJSON ) ) {
1411
- if ( value instanceof Backbone.Collection ) {
1412
- json[ rel.keyDestination ] = value.pluck( rel.options.includeInJSON );
1455
+ else if ( _.isString( includeInJSON ) ) {
1456
+ if ( related instanceof Backbone.Collection ) {
1457
+ value = related.pluck( includeInJSON );
1413
1458
  }
1414
- else if ( value instanceof Backbone.Model ) {
1415
- json[ rel.keyDestination ] = value.get( rel.options.includeInJSON );
1459
+ else if ( related instanceof Backbone.Model ) {
1460
+ value = related.get( includeInJSON );
1416
1461
  }
1417
- else {
1418
- json[ rel.keyDestination ] = null;
1462
+
1463
+ // Add ids for 'unfound' models if includeInJSON is equal to (only) the relatedModel's `idAttribute`
1464
+ if ( includeInJSON === rel.relatedModel.prototype.idAttribute ) {
1465
+ if ( rel instanceof Backbone.HasMany ) {
1466
+ value = value.concat( rel.keyIds );
1467
+ }
1468
+ else if ( rel instanceof Backbone.HasOne ) {
1469
+ value = value || rel.keyId;
1470
+ }
1419
1471
  }
1420
1472
  }
1421
- else if ( _.isArray( rel.options.includeInJSON ) ) {
1422
- if ( value instanceof Backbone.Collection ) {
1423
- var valueSub = [];
1424
- value.each( function( model ) {
1473
+ else if ( _.isArray( includeInJSON ) ) {
1474
+ if ( related instanceof Backbone.Collection ) {
1475
+ value = [];
1476
+ related.each( function( model ) {
1425
1477
  var curJson = {};
1426
- _.each( rel.options.includeInJSON, function( key ) {
1478
+ _.each( includeInJSON, function( key ) {
1427
1479
  curJson[ key ] = model.get( key );
1428
1480
  });
1429
- valueSub.push( curJson );
1481
+ value.push( curJson );
1430
1482
  });
1431
- json[ rel.keyDestination ] = valueSub;
1432
1483
  }
1433
- else if ( value instanceof Backbone.Model ) {
1434
- var valueSub = {};
1435
- _.each( rel.options.includeInJSON, function( key ) {
1436
- valueSub[ key ] = value.get( key );
1484
+ else if ( related instanceof Backbone.Model ) {
1485
+ value = {};
1486
+ _.each( includeInJSON, function( key ) {
1487
+ value[ key ] = related.get( key );
1437
1488
  });
1438
- json[ rel.keyDestination ] = valueSub;
1439
- }
1440
- else {
1441
- json[ rel.keyDestination ] = null;
1442
1489
  }
1443
1490
  }
1444
1491
  else {
1445
1492
  delete json[ rel.key ];
1446
1493
  }
1447
1494
 
1495
+ if ( includeInJSON ) {
1496
+ json[ rel.keyDestination ] = value;
1497
+ }
1498
+
1448
1499
  if ( rel.keyDestination !== rel.key ) {
1449
1500
  delete json[ rel.key ];
1450
1501
  }
@@ -1548,17 +1599,15 @@
1548
1599
  // inherited automatically (due to a redefinition of 'relations').
1549
1600
  // Otherwise, make sure we don't get here again for this type by making '_superModel' false so we fail
1550
1601
  // the isUndefined/isNull check next time.
1551
- if ( this._superModel ) {
1552
- //
1553
- if ( this._superModel.prototype.relations ) {
1554
- var supermodelRelationsExist = _.any( this.prototype.relations || [], function( rel ) {
1555
- return rel.model && rel.model !== this;
1602
+ if ( this._superModel && this._superModel.prototype.relations ) {
1603
+ // Find relations that exist on the `_superModel`, but not yet on this model.
1604
+ var inheritedRelations = _.select( this._superModel.prototype.relations || [], function( superRel ) {
1605
+ return !_.any( this.prototype.relations || [], function( rel ) {
1606
+ return superRel.relatedModel === rel.relatedModel && superRel.key === rel.key;
1556
1607
  }, this );
1608
+ }, this );
1557
1609
 
1558
- if ( !supermodelRelationsExist ) {
1559
- this.prototype.relations = this._superModel.prototype.relations.concat( this.prototype.relations );
1560
- }
1561
- }
1610
+ this.prototype.relations = inheritedRelations.concat( this.prototype.relations );
1562
1611
  }
1563
1612
  else {
1564
1613
  this._superModel = false;
@@ -1598,6 +1647,9 @@
1598
1647
  // If not, create an instance (unless 'options.create' is false).
1599
1648
  if ( _.isObject( attributes ) ) {
1600
1649
  if ( model && options.merge !== false ) {
1650
+ // Make sure `options.collection` doesn't cascade to nested models
1651
+ delete options.collection;
1652
+
1601
1653
  model.set( parsedAttributes, options );
1602
1654
  }
1603
1655
  else if ( !model && options.create !== false ) {
@@ -1627,7 +1679,7 @@
1627
1679
  model = attrs;
1628
1680
  }
1629
1681
  else {
1630
- options || (options = {});
1682
+ options || ( options = {} );
1631
1683
  options.collection = this;
1632
1684
 
1633
1685
  if ( typeof this.model.findOrCreate !== 'undefined' ) {
@@ -1648,19 +1700,23 @@
1648
1700
 
1649
1701
 
1650
1702
  /**
1651
- * Override Backbone.Collection.add, so we'll create objects from attributes where required,
1703
+ * Override Backbone.Collection.set, so we'll create objects from attributes where required,
1652
1704
  * and update the existing models. Also, trigger 'relational:add'.
1653
1705
  */
1654
- var add = Backbone.Collection.prototype.__add = Backbone.Collection.prototype.add;
1655
- Backbone.Collection.prototype.add = function( models, options ) {
1706
+ var set = Backbone.Collection.prototype.__set = Backbone.Collection.prototype.set;
1707
+ Backbone.Collection.prototype.set = function( models, options ) {
1656
1708
  // Short-circuit if this Collection doesn't hold RelationalModels
1657
1709
  if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
1658
- return add.apply( this, arguments );
1710
+ return set.apply( this, arguments );
1659
1711
  }
1660
1712
 
1661
- models = _.isArray( models ) ? models.slice() : [ models ];
1662
- // Set default options to the same values as `add` uses, so `findOrCreate` will also respect those.
1663
- options = _.extend( { merge: false }, options );
1713
+ if ( options && options.parse ) {
1714
+ models = this.parse( models, options );
1715
+ }
1716
+
1717
+ if ( !_.isArray( models ) ) {
1718
+ models = models ? [ models ] : [];
1719
+ }
1664
1720
 
1665
1721
  var newModels = [],
1666
1722
  toAdd = [];
@@ -1686,7 +1742,8 @@
1686
1742
  }, this );
1687
1743
 
1688
1744
  // Add 'models' in a single batch, so the original add will only be called once (and thus 'sort', etc).
1689
- add.call( this, toAdd, options );
1745
+ // If `parse` was specified, the collection and contained models have been parsed now.
1746
+ set.call( this, toAdd, _.defaults( { parse: false }, options ) );
1690
1747
 
1691
1748
  _.each( newModels, function( model ) {
1692
1749
  // Fire a `relational:add` event for any model in `newModels` that has actually been added to the collection.
@@ -1735,6 +1792,7 @@
1735
1792
  */
1736
1793
  var reset = Backbone.Collection.prototype.__reset = Backbone.Collection.prototype.reset;
1737
1794
  Backbone.Collection.prototype.reset = function( models, options ) {
1795
+ options = _.extend( { merge: true }, options );
1738
1796
  reset.call( this, models, options );
1739
1797
 
1740
1798
  if ( this.model.prototype instanceof Backbone.RelationalModel ) {
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.8.0
4
+ version: 0.8.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-07 00:00:00.000000000 Z
12
+ date: 2013-04-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: railties
@@ -56,7 +56,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
56
56
  version: '0'
57
57
  segments:
58
58
  - 0
59
- hash: -4537690354262866802
59
+ hash: -3926763858083606606
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: -4537690354262866802
68
+ hash: -3926763858083606606
69
69
  requirements: []
70
70
  rubyforge_project:
71
71
  rubygems_version: 1.8.24