Last active
December 18, 2020 18:44
-
-
Save amkirwan/43faf1d85b227911f331affa9094ae37 to your computer and use it in GitHub Desktop.
Ember.js rollback model and relationships
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//mixins/rollback-relationships.js | |
import Ember from 'ember'; | |
import DS from 'ember'; | |
export default Ember.Mixin.create({ | |
cacheRelationships: Ember.computed(function() { return Ember.A(); }), | |
tmpRecords: Ember.computed(function() { return Ember.A(); }), | |
dirtyRelationships: Ember.computed(function() { return Ember.A(); }), | |
originalRelationships: Ember.computed(function() { return Ember.Object.create() }), | |
hasDirtyRelationship: Ember.computed(function() { return false; }), | |
dirtyTracker: Ember.Object.create(), | |
// use it to check if the model has a dirty attribute or a dirty relationship | |
isExtraDirty: Ember.computed('dirtyTracker', function() { | |
if (this.get('hasDirtyAttributes')) { return true; } | |
let dirty = false; | |
let dirtyTracker = this.get('dirtyTracker'); | |
let keys = Object.keys(dirtyTracker); | |
for (let i=0, len=keys.length; i < len; i++) { | |
if (dirtyTracker[keys[i]] === true) { | |
dirty = true; | |
break; | |
} | |
} | |
return dirty; | |
}).volatile(), | |
dirtyRelationshipTracking: Ember.on('init', function() { | |
let model = this; | |
model.get('cacheRelationships').forEach((key) => { | |
let rel = model.relationshipFor(key); | |
if (rel.kind === 'hasMany') { | |
model.addObserver(`${key}[email protected]`, (sender, key, value, rev) => { | |
Ember.run.once(() => { | |
let attrRelationship = key.split(/\./)[0] | |
let dirtyModels = sender.get(attrRelationship).filterBy('hasDirtyAttributes', true); | |
if (Ember.isEmpty(dirtyModels)) { | |
this.get('dirtyTracker').set(attrRelationship, false); | |
this.set('hasDirtyRelationship', false); | |
this.get('dirtyRelationships').removeObject(attrRelationship); | |
} else { | |
this.get('dirtyTracker').set(attrRelationship, true); | |
this.set('hasDirtyRelationship', true); | |
this.get('dirtyRelationships').addObject(attrRelationship); | |
} | |
}); | |
}); | |
} else if (rel.kind === 'belongsTo') { | |
model.addObserver(`${key}`, (sender, key, value, rev) => { | |
Ember.run.once(() => { | |
let attrRelationship = key; | |
// do not check if the relationship is undefined | |
if (this.get(`originalRelationships.${key}`)) { | |
if (Ember.isEqual(this.get(`${key}.id`), this.get(`originalRelationships.${key}`))) { | |
this.get('dirtyTracker').set(attrRelationship, false); | |
this.set('hasDirtyRelationship', false); | |
this.get('dirtyRelationships').removeObject(key); | |
} else { | |
this.get('dirtyTracker').set(attrRelationship, true); | |
this.set('hasDirtyRelationship', true); | |
this.get('dirtyRelationships').addObject(key); | |
} | |
} else { | |
this.get('dirtyTracker').set(attrRelationship, false); | |
} | |
}); | |
}); | |
} | |
}); | |
}), | |
relationshipsCacheAll() { | |
let model = this; | |
if (model.get('cacheRelationships').length > 0) { | |
model.get('cacheRelationships').forEach((key) => { | |
let rel = model.relationshipFor(key); | |
if (rel.kind === 'belongsTo') { | |
this._cacheBelongsTo(key); | |
} | |
if (rel.kind === 'hasMany') { | |
this._cacheHasMany(key); | |
} | |
}); | |
} | |
}, | |
relationshipsCache() { | |
this.clearTmpRecords(); | |
this.cacheOriginalRelationships(); | |
}, | |
_cacheBelongsTo(key) { | |
let model = this; | |
let belongsRef = model.belongsTo(key); | |
if (belongsRef.belongsToRelationship.isAsync) { | |
model.get(key).then((m) => { | |
m.relationshipsCacheAll(); | |
}); | |
} else { | |
model.get(key).relationshipsCacheAll(); | |
} | |
model.relationshipsCache(); | |
}, | |
_cacheHasMany(key) { | |
let model = this; | |
let manyRef = model.hasMany(key); | |
if (manyRef.hasManyRelationship.isAsync) { | |
model.get(key).then((m) => { | |
m.invoke('relationshipsCacheAll'); | |
}); | |
} else { | |
model.get(key).forEach((m) => { | |
m.invoke('relationshipsCacheAll'); | |
}); | |
} | |
model.relationshipsCache(); | |
}, | |
createRecord(name) { | |
let record = this.store.createRecord(name); | |
this.get('tmpRecords').pushObject(record); | |
return record; | |
}, | |
destroyTmpRecords() { | |
this.get('tmpRecords').invoke('destroyRecord'); | |
this.clearTmpRecords(); | |
}, | |
clearTmpRecords() { | |
this.get('tmpRecords').clear(); | |
}, | |
rollbackAll() { | |
let model = this; | |
if (model.get('isExtraDirty')) { | |
if (model.get('cacheRelationships').length === 0) { | |
model.rollbackModelAttributes(); | |
model.destroyTmpRecords(); | |
return; | |
} else { | |
model.get('cacheRelationships').forEach((key) => { | |
let rel = model.relationshipFor(key); | |
if (rel.kind === 'belongsTo') { | |
this._rollbackBelongsTo(key); | |
} | |
if (rel.kind === 'hasMany') { | |
this._rollbackHasMany(key); | |
} | |
}); | |
} | |
} | |
}, | |
_rollbackBelongsTo(key) { | |
let model = this; | |
let belongsRef = model.belongsTo(key); | |
if (belongsRef.belongsToRelationship.isAsync) { | |
model.get(key).then((m) => { | |
m.rollbackAll(); | |
}); | |
} else { | |
model.get(key).rollbackAll(); | |
} | |
model.rollbackRelationships(); | |
}, | |
_rollbackHasMany(key) { | |
let model = this; | |
let manyRef = model.hasMany(key); | |
if (manyRef.hasManyRelationship.isAsync) { | |
model.get(key).then((m) => { | |
m.invoke('rollbackAll'); | |
}); | |
} else { | |
model.get(key).forEach((m) => { | |
m.invoke('rollbackAll'); | |
}); | |
} | |
model.rollbackRelationships(); | |
}, | |
rollbackRelationships() { | |
let model = this; | |
if (this.get('cacheRelationships').length === 0) { | |
return; | |
} else { | |
this.get('cacheRelationships').forEach((key) => { | |
this.rollbackModelAttributes(); | |
this._rollbackOriginalRelationships(key); | |
}); | |
} | |
}, | |
cacheOriginalRelationships() { | |
let model = this; | |
if (this.get('cacheRelationships').length === 0) { | |
model.eachRelationship((key, relationship) => { | |
this._setOriginalRelationships(key); | |
}); | |
} else { | |
this.get('cacheRelationships').forEach((key) => { | |
this._setOriginalRelationships(key); | |
}); | |
} | |
}, | |
rollbackModelAttributes() { | |
let model = this; | |
if (model.get('hasDirtyAttributes')) { | |
model.rollbackAttributes(); | |
} | |
}, | |
_setOriginalRelationships(key) { | |
let model = this; | |
let or = model.get('originalRelationships'); | |
let rel = model.relationshipFor(key); | |
if (rel.kind === 'belongsTo') { | |
or.set(key, model.belongsTo(key).id()); | |
} | |
if (rel.kind === 'hasMany') { | |
or.set(key, model.hasMany(key).ids()); | |
} | |
}, | |
_rollbackOriginalRelationships(key) { | |
let model = this; | |
let rel = model.relationshipFor(key); | |
if (rel.kind === 'belongsTo') { | |
let rel_id = model.get(`originalRelationships.${key}`); | |
if (!Ember.isEmpty(rel_id)) { | |
model.set(key, this.store.peekRecord(key, rel_id)); | |
} | |
} | |
if (rel.kind === 'hasMany') { | |
let rel_ids = model.get(`originalRelationships.${key}`); | |
if (!Ember.isEmpty(rel_ids)) { | |
model.get(key).clear(); | |
rel_ids.forEach((rel_id) => { | |
model.get(key).pushObject(this.store.peekRecord(Ember.String.singularize(key), rel_id)); | |
}); | |
} | |
} | |
} | |
}); | |
// Then import rollback-relationships into your model | |
import attr from 'ember-data/attr'; | |
import Model from 'ember-data/model'; | |
import { hasMany } from 'ember-data/relationships'; | |
import Rollback from 'app/mixins/rollback-relationships'; | |
export default Model.extend(Rollback, { | |
username: attr('string'), | |
firstname: attr('string'), | |
middlename: attr('string'), | |
lastname: attr('string'), | |
phoneNumbers: hasMany('phoneNumber', { async: true }), | |
emailAddresses: hasMany('emailAddress', { async: true }), | |
cacheRelationships: ['phoneNumbers', 'emailAddresses'], | |
}); | |
// Then in your route call relationshipsCacheAll() to cache the current relationships of the model | |
// And to rollback the relationships call rollbackAll() | |
// user/edit/route.js | |
import Ember from 'ember'; | |
export default Ember.Route.extend({ | |
afterModel(model, transition) { | |
model.relationshipsCacheAll(); | |
} | |
actions: { | |
submit(mode) { | |
model.save(); | |
}, | |
cancelSave(model) { | |
model.rollbackAll(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@amkirwan thanks for sharing this code. I'm getting a
"Assertion Failed: '{}' does not appear to be an ember-data model"
as soon as I import the Mixin into my Model. Any ideas what could be wrong? Thanks.