If you have worked much with Ember-Data, then you know the buffered object proxy pattern:
http://coryforsyth.com/2013/06/27/ember-buffered-proxy-and-method-missing/
This pattern allows us to put changes to a model into a buffer instead of on the model itself. There are several benefits of this:
- If the model has changes come from a push source (an attribute is updated), the changes will not destroy what the user is currently entering.
- Saves can be de-coupled from dirty tracking. If you are saving a model as a user types, the model in flight will not accept changes from the user. You will get the dreaded
cannot change an object inFlight
error. With a buffer, you can commit the buffer to the model and save the model, then allow the user to keep making edits against a new buffer. No conflict. - In general, a buffer allows you to treat Ember-Data records more like database rows. You lock them for modification (while persisting them) but only for the shortest time possible. An object is made dirty then saved immediately, it doesn't sit in a dirty state the whole time the user is editing it.
- In general, it encourages you to be wary of the store as a global state. When a user is editing a model, why is that model in a certain state across the whole application? If you modify a user's email address, you should not see their email on the top right of the screen changing in real-time.
But the object buffer proxy is limited to a simple model with basic properties. It cannot buffer complex changes, such as buffering changes to a relationship, or tracking dirty state across multiple objects.
I propose a buffered store proxy. Something like:
App.User = DS.Model.extend({
name: DS.attr('string'),
addresses: DS.hasMany({async: true}),
phones: DS.hasMany({async: true})
});
App.Address = DS.Model.extend({
user: DS.belongsTo()
});
App.Phone = DS.Model.extend({
user: DS.belongsTo()
});
var storeProxy = new BufferedStoreProxy(store, [App.User, App.Address]);
Ember.RSVP.Promise.all([store.find('user', 'me'), store.find('addresses', {user: 'me'})]).then(function(results){
var user = results[0],
addresses = results[1];
var userProxy = storeProxy.add(user);
storeProxy.add(addresses);
storeProxy.get('isDirty'); // => false
userProxy.get('addresses'); // => any addresses already in the proxy
userProxy.get('phone'); // => Ember-Data phone relation
storeProxy.createRecord('address', {user: userProxy});
storeProxy.get('isDirty'); // => true
userProxy.set('name', 'new name');
storeProxy.get('isDirty'); // => true
userProxy.get('isDirty'); // => true
userProxy.get('name'); // => null
user.get('isDirty'); // => false
storeProxy.commit();
userProxy.get('name'); // => 'new name'
user.get('isDirty'); // => true
return store.commit(); // => saves the new address and the user.
});
This proxy would use a second store instance, and proxy call against itself to the internal store. For each action (such as createRecord
) it would track the operation to be perform and perform that action on its own store. When commit
is called, it will perform those actions on the store passed in during instantiation. If rollback is called, it will clear the operations.
The provided classes at instantiation will provide the bounderies of what models to return as proxies. The buffer will only populate its internal store with objects added to it. Anything not passed in explicitly via add
will be not found or be considered a new record. Maybe there are no find
or fetch
commands on this proxy store at all.
Hi mixonic, do you have any working example of this BufferedStoreProxy approach?