Skip to content

Instantly share code, notes, and snippets.

@samselikoff
Last active May 12, 2016 17:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samselikoff/ff0d5f825a0969f82a1bc8889dba6d15 to your computer and use it in GitHub Desktop.
Save samselikoff/ff0d5f825a0969f82a1bc8889dba6d15 to your computer and use it in GitHub Desktop.
Example of batch/bulk operations
// adapters/author.js
/*
Example use. This tells ED that the backend supports bulk operations on PUT and POST.
What this means is, if multiple author requests of the same type occur in a single
tick of the Ember Run loop, this adapter will coalesce them into a single response.
Say we have
author1.save();
author2.save();
Typically this would trigger two network requests:
PUT /authors/1
{
author: {
id: 1,
name: 'John Smith'
}
}
PUT /authors/2
{
author: {
id: 2,
name: 'Susan Smith'
}
}
Since we've said that our backend supports bulk operations, now the adapter would roll
these into a single request:
PUT /authors
{
authors: [
{
id: 1,
name: 'John Smith'
},
{
id: 2,
name: 'Susan Smith'
}
}
}
The backend, if successful, should respond with the array, and the
bulk operations mixin above will resolve each of the original requests
with the respective payload (which will correctly notify ED of the new
server data.)
Note these files were tied to a project I was working on, there are some
parts that would probably need to change to work with your particular server
format.
The bulk endpoint conventions are taken from an early version of the json:api
bulk extension: https://github.com/json-api/json-api/blob/9c7a03dbc37f80f6ca81b16d444c960e96dd7a57/extensions/bulk/index.md
*/
import ApplicationAdapter from './application';
import BulkOperations from '../mixins/bulk-operations';
export default ApplicationAdapter.extend(BulkOperations, {
bulkCollectionName: 'answer', // this is dumb, really should be able to introspect the AnswerAdapter to get this, but couldn't figure it out
bulkOperations: ['PUT', 'POST']
});
// mixins/batch-persist.js
import Ember from 'ember';
var queue = [];
export default Ember.Mixin.create({
/**
* Override this method to actually make your batch API call, then
* resolve/reject each request.
*/
batch: function (requests) {
throw new Error('You need to implement the batch() method. See https://github.com/jayphelps/ember-model-batch/blob/master/README.md');
},
_flushQueue: function () {
var ajaxHook = this.ajax,
ajaxOriginal = this.constructor.__super__.ajax;
// Reset original ajax method so they can use it if they'd like
this.ajax = ajaxOriginal;
// Run batch with a deep copy so no side-effects if they alter it
this.batch(Ember.copy(queue, true));
// Empty the queue and add our hook back;
queue.length = 0;
this.ajax = ajaxHook;
},
ajax(...args) {
// TODO: This is suuper brittle, I believe __nextSuper changes in 2.1ish. Should
// be able to make it just _super then.
let _super = this.__nextSuper.bind(this);
return new Ember.RSVP.Promise((resolve, reject) => {
queue.push({
args: args,
resolve,
reject
});
Ember.run.later(() => {
if (!queue.length) {
return;
}
if (queue.length === 1) {
queue.length = 0;
return _super(...args).then(resolve, reject);
} else {
Ember.run.once(this, this._flushQueue);
}
});
});
}
});
// mixins/bulk-operations.js
import Ember from 'ember';
import BatchPersist from './batch-persist';
const { pluralize } = Ember.String;
export default Ember.Mixin.create(BatchPersist, {
bulkOperations: [],
batch(originalRequests) {
let collectionName = this.get('bulkCollectionName');
let groupedRequests = originalRequests.reduce((memo, request) => {
let type = request.args[1];
memo[type].push(request);
return memo;
}, {GET: [], POST: [], PUT: [], PATCH: [], 'DELETE': []});
let finalRequests = Object.keys(groupedRequests).reduce((memo, method) => {
let requests = groupedRequests[method];
if (!requests.length) {
return memo;
}
if ((this.get('bulkOperations').indexOf(method) > -1) && (requests.length > 1)) {
let payload = requests
.map(req => {
let attrs = req.args[2].data[collectionName];
if (method === 'PUT') {
/*
This is really ugly, have to get the `id` from the model's original PUT url
*/
let path = req.args[0];
let id = path.substr(path.lastIndexOf('/') + 1);
attrs.id = id;
}
return attrs;
});
let url = this.buildURL(collectionName);
let data = JSON.stringify({ [pluralize(collectionName)]: payload });
return [...memo, this._bulkRequest(method, url, data, requests)];
} else {
return memo.concat(requests
.map(request => {
let data = (request.args[2] && request.args[2].data) ? request.args[2].data : null;
return Ember.$.ajax({
type: request.args[1],
url: request.args[0],
data: data ? JSON.stringify(data) : undefined,
headers: {
'Content-Type': 'application/json'
}
}).then(request.resolve, request.reject);
})
);
}
}, []);
return finalRequests;
},
_bulkRequest(type, url, data, originalRequests) {
return Ember.$.ajax({
type,
url,
data,
headers: {
'Content-Type': 'application/json'
}
})
.then((payload) => {
let bulkRoot = Object.keys(JSON.parse(data))[0];
let resolvedRecords = payload[bulkRoot];
let singularRoot = bulkRoot.singularize();
return originalRequests.map((req, i) => {
return req.resolve({[singularRoot]: resolvedRecords[i]});
});
})
.fail(() => {
return originalRequests.map(req => req.reject());
});
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment