Skip to content

Commit

Permalink
API change: make fetchRelated return a single promise. Ref #467
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulUithol committed Jun 4, 2014
1 parent 742796a commit b7e7123
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 37 deletions.
4 changes: 2 additions & 2 deletions backbone-relational.js
Expand Up @@ -1387,7 +1387,7 @@
* @param {string} key The relation key to fetch models for. * @param {string} key The relation key to fetch models for.
* @param {Object} [options] Options for 'Backbone.Model.fetch' and 'Backbone.sync'. * @param {Object} [options] Options for 'Backbone.Model.fetch' and 'Backbone.sync'.
* @param {Boolean} [refresh=false] Fetch existing models from the server as well (in order to update them). * @param {Boolean} [refresh=false] Fetch existing models from the server as well (in order to update them).
* @return {jQuery.when[]} An array of request objects. * @return {jQuery.Deferred} A jQuery promise object
*/ */
fetchRelated: function( key, options, refresh ) { fetchRelated: function( key, options, refresh ) {
// Set default `options` for fetch // Set default `options` for fetch
Expand Down Expand Up @@ -1458,7 +1458,7 @@
} }
} }


return requests; return $.when.apply( null, requests );
}, },


set: function( key, value, options ) { set: function( key, value, options ) {
Expand Down
17 changes: 9 additions & 8 deletions index.html
Expand Up @@ -517,8 +517,8 @@ <h4 class="code">
<p> <p>
Determine the type of collections used for a HasMany relation. If you define a Determine the type of collections used for a HasMany relation. If you define a
url(models&lt;Backbone.Model[]&gt;) function on the specified collection, this enables url(models&lt;Backbone.Model[]&gt;) function on the specified collection, this enables
<a href="#RelationalModel-fetchRelated"><q>fetchRelated</q></a> to fetch all missing models in one request, instead of <a href="#RelationalModel-fetchRelated"><q>fetchRelated</q></a> to fetch all missing models in one request,
firing a separate request for each. instead of firing a separate request for each.
</p> </p>


<h4 class="code" id="relations-collectionKey"> <h4 class="code" id="relations-collectionKey">
Expand Down Expand Up @@ -793,16 +793,17 @@ <h4 class="code">
fetchRelated<code>relationalModel.fetchRelated(key&lt;string&gt;, [options&lt;object&gt;], [refresh&lt;boolean&gt;])</code> fetchRelated<code>relationalModel.fetchRelated(key&lt;string&gt;, [options&lt;object&gt;], [refresh&lt;boolean&gt;])</code>
</h4> </h4>
<p> <p>
Returns: <q>deferred[]</q> An array of (zero or more) request objects. Returns: <q>jQuery.Deferred</q> A <a href="http://api.jquery.com/category/deferred-object/">jQuery promise</a>
</p> </p>
<p> <p>
Fetch models from the server that were referenced in the model's attributes, but have not been found/created yet. Fetch the models in a relation from the server. By default, only those models are fetched that were referenced in the attributes,
This can be used specifically for lazy-loading scenarios. Setting update to true guarantees that the model will but have not been found/created yet. This can be used specifically for lazy-loading scenarios.
be fetched from the server and any model that already exists in the store will be updated with the retrieved data. Setting <q>refresh</q> to true will fetch all model(s) from the server. In that case, any model that already
The options object specifies options to be passed to <a href="http://backbonejs.org/#Sync">Backbone.Sync</a>. exists will be updated with the retrieved data.
The <q>options</q> object specifies options to be passed to <a href="http://backbonejs.org/#Sync">Backbone.Sync</a>.
</p> </p>
<p> <p>
By default, a separate request will be fired for each additional model that is to be fetched from the server. By default, a separate request will be fired for each model that is to be fetched from the server (if `key` references a collection).
However, if your server/API supports it, you can fetch the set of models in one request by specifying a 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 <a href="#relations-collectionType"><q>collectionType</q></a> collectionType for the relation you call fetchRelated on. The <a href="#relations-collectionType"><q>collectionType</q></a>
should have an overridden <a href="http://backbonejs.org/#Collection-url"><q>url</q></a> should have an overridden <a href="http://backbonejs.org/#Collection-url"><q>url</q></a>
Expand Down
60 changes: 33 additions & 27 deletions test/tests.js
Expand Up @@ -959,19 +959,19 @@ $(document).ready(function() {
var idsToFetch = person.getIdsToFetch( 'user' ); var idsToFetch = person.getIdsToFetch( 'user' );
deepEqual( idsToFetch, [ 'user-10' ] ); deepEqual( idsToFetch, [ 'user-10' ] );


var requests = person.fetchRelated( 'user', { error: function() { var request = person.fetchRelated( 'user', { error: function() {
errorCount++; errorCount++;
} }
}); });


ok( _.isArray( requests ) ); ok( _.isObject( request ) && request.always && request.done && request.fail );
equal( requests.length, 1, "A request has been made" ); equal( window.requests.length, 1, "A single request has been made" );
ok( person.get( 'user' ) instanceof User ); ok( person.get( 'user' ) instanceof User );


// Triggering the 'error' callback should destroy the model // Triggering the 'error' callback should destroy the model
requests[ 0 ].error(); window.requests[ 0 ].error();
// Trigger the 'success' callback to fire the 'destroy' event // Trigger the 'success' callback on the `destroy` call to actually fire the 'destroy' event
window.requests[ window.requests.length - 1 ].success(); _.last( window.requests ).success();


ok( !person.get( 'user' ), "User has been destroyed & removed" ); ok( !person.get( 'user' ), "User has been destroyed & removed" );
equal( errorCount, 1, "The error callback executed successfully" ); equal( errorCount, 1, "The error callback executed successfully" );
Expand All @@ -981,8 +981,8 @@ $(document).ready(function() {
resource_uri: 'person-11' resource_uri: 'person-11'
}); });


requests = person2.fetchRelated( 'user' ); request = person2.fetchRelated( 'user' );
equal( requests.length, 0, "No request was made" ); equal( window.requests.length, 1, "No request was made" );
}); });


test( "fetchRelated on a HasMany relation", function() { test( "fetchRelated on a HasMany relation", function() {
Expand All @@ -997,14 +997,16 @@ $(document).ready(function() {
// //
// Case 1: separate requests for each model // Case 1: separate requests for each model
// //
var requests = zoo.fetchRelated( 'animals', { error: function() { errorCount++; } } ); window.requests = [];
ok( _.isArray( requests ) ); var request = zoo.fetchRelated( 'animals', { error: function() { errorCount++; } } );
equal( requests.length, 2, "Two requests have been made (a separate one for each animal)" );
ok( _.isObject( request ) && request.always && request.done && request.fail );
equal( window.requests.length, 2, "Two requests have been made (a separate one for each animal)" );
equal( zoo.get( 'animals' ).length, 3, "Three animals in the zoo" ); equal( zoo.get( 'animals' ).length, 3, "Three animals in the zoo" );


// Triggering the 'error' callback for one request should destroy the model // Triggering the 'error' callback for one request should destroy the model
requests[ 0 ].error(); window.requests[ 0 ].error();
// Trigger the 'success' callback on the `destroy` call to fire the 'destroy' event // Trigger the 'success' callback on the `destroy` call to actually fire the 'destroy' event
_.last( window.requests ).success(); _.last( window.requests ).success();


equal( zoo.get( 'animals' ).length, 2, "Two animals left in the zoo" ); equal( zoo.get( 'animals' ).length, 2, "Two animals left in the zoo" );
Expand All @@ -1014,7 +1016,9 @@ $(document).ready(function() {
// Case 2: one request per fetch (generated by the collection) // Case 2: one request per fetch (generated by the collection)
// //
// Give 'zoo' a custom url function that builds a url to fetch a set of models from their ids // Give 'zoo' a custom url function that builds a url to fetch a set of models from their ids
window.requests = [];
errorCount = 0; errorCount = 0;

zoo.get( 'animals' ).url = function( models ) { zoo.get( 'animals' ).url = function( models ) {
return '/animal/' + ( models ? 'set/' + _.pluck( models, 'id' ).join(';') + '/' : '' ); return '/animal/' + ( models ? 'set/' + _.pluck( models, 'id' ).join(';') + '/' : '' );
}; };
Expand All @@ -1025,37 +1029,39 @@ $(document).ready(function() {
equal( zoo.get( 'animals' ).length, 1 ); equal( zoo.get( 'animals' ).length, 1 );


// `fetchRelated` creates two placeholder models for the ids present in the relation. // `fetchRelated` creates two placeholder models for the ids present in the relation.
requests = zoo.fetchRelated( 'animals', { error: function() { errorCount++; } } ); window.requests = [];
request = zoo.fetchRelated( 'animals', { error: function() { errorCount++; } } );


ok( _.isArray( requests ) ); ok( _.isObject( request ) && request.always && request.done && request.fail );
equal( requests.length, 1 ); equal( window.requests.length, 1 );
equal( requests[ 0 ].url, '/animal/set/lion-2;zebra-2/' ); equal( _.last( window.requests ).url, '/animal/set/lion-2;zebra-2/' );
equal( zoo.get('animals').length, 3 ); equal( zoo.get('animals').length, 3, "Three animals in the zoo" );


// Triggering the 'error' callback (some error occured during fetching) should trigger the 'destroy' event // Triggering the 'error' callback (some error occured during fetching) should trigger the 'destroy' event
// on both fetched models, but should NOT actually make 'delete' requests to the server! // on both fetched models, but should NOT actually make 'delete' requests to the server!
var numRequests = window.requests.length; _.last( window.requests ).error();
requests[ 0 ].error(); equal( window.requests.length, 1, "An error occured when fetching, but no DELETE requests are made to the server while handling local cleanup." );
ok( window.requests.length === numRequests, "An error occured when fetching, but no DELETE requests are made to the server while handling local cleanup." );


equal( zoo.get( 'animals' ).length, 1, "Both animals are destroyed" ); equal( zoo.get( 'animals' ).length, 1, "Both animals are destroyed" );
equal( errorCount, 2, "The error callback executed successfully for both models" ); equal( errorCount, 2, "The error callback executed successfully for both models" );


// Try to re-fetch; nothing left to get though // Try to re-fetch; nothing left to get though
requests = zoo.fetchRelated( 'animals' ); window.requests = [];
request = zoo.fetchRelated( 'animals' );


equal( requests.length, 0 ); equal( window.requests.length, 0 );
equal( zoo.get( 'animals' ).length, 1 ); equal( zoo.get( 'animals' ).length, 1 );


// Re-fetch the existing model // Re-fetch the existing model
requests = zoo.fetchRelated( 'animals', null, true ); window.requests = [];
request = zoo.fetchRelated( 'animals', null, true );


equal( requests.length, 1 ); equal( window.requests.length, 1 );
equal( requests[ 0 ].url, '/animal/set/monkey-1/' ); equal( _.last( window.requests ).url, '/animal/set/monkey-1/' );
equal( zoo.get( 'animals' ).length, 1 ); equal( zoo.get( 'animals' ).length, 1 );


// An error while refreshing an existing model shouldn't affect it // An error while refreshing an existing model shouldn't affect it
requests[ 0 ].error(); window.requests[ 0 ].error();
equal( zoo.get( 'animals' ).length, 1 ); equal( zoo.get( 'animals' ).length, 1 );
}); });


Expand Down

0 comments on commit b7e7123

Please sign in to comment.