jeudi 4 mai 2017

How to restore model after failed save?

We are using ember-changeset to handle our data in forms. Data flow is usually like this: input -> changeset -> model -> server -> model -> changeset -> input. User change some input which is reflected in changeset and validated. When user presses submit changeset is baked into model which is then saved.

And here is the problem. If server refuses to accept changes then we end up with model in broken state. To overcome this we implemented custom save method to mimic what model.save does. But it uses private methods and since ember-data@2.10 not working on relations - or to be more precise it works but does not wait for them to reload.

Any help or idea would be greatly appreciated.

/*
 * Saves an object by saving changes taken from a changeset.
 * Updates the target object if the operation succeeds.
 * @param changeset the changeset to save
 * @return {Promise} a promise that resolves to an object returned by the server
 */
saveChangeset(changeset) {
  const record = changeset.get('_content'); // not entirely a public field
  const id = record.get('id');
  const constructor = record.get('constructor');
  const modelName = constructor.modelName;

  const snapshot = changesetToSnapshot(changeset);
  const operation = record.get('isNew') ? 'createRecord' : 'updateRecord';

  record._internalModel.adapterWillCommit();
  return this.adapterFor(modelName)[operation](this, constructor, snapshot)
    .then(res => {
      // propagate the changes
      const model = this.modelFor(modelName);
      const serializer = this.serializerFor(modelName);
      const payload = serializer.normalizeResponse(this, model, res, id, operation);
      if (payload.included) {
        this.push({data: payload.included});
      }
      this.didSaveRecord(record._internalModel, {data: payload.data});
      changeset.rollback();
      return res;
    });
},


/**
 * Creates s snapshot-like object based on a changeset.
 */
export default function changesetToSnapshot(changeset) {
  const model = changeset.get('_content'); // not entirely a public field
  const isNew = model.get('isNew');
  const id = model.get('id');
  const constructor = model.get('constructor');
  const modelName = constructor.modelName;

  const changes = {};
  changeset.get('changes').forEach(change => {
    changes[change.key] = null; // The value does not matter
  });

  const filterByName = fn => (name, meta) => { // eslint-disable-line func-style
    if (name in changes) {
      fn(name, meta);
    }
  };

  const fieldFilter = isNew ? fn => fn : filterByName;

  return { // emulate a snapshot
    _isChangeset: true,
    type: constructor,
    record: model,
    modelName,
    id,
    eachAttribute: fn => model.eachAttribute(fieldFilter(fn)),
    eachRelationship: fn => model.eachRelationship(fieldFilter(fn)),
    attr: key => changeset.get(key),
    belongsTo(key, options) {
      assert('Snapshot from changeset can only return the id of a belongsTo relationship, not a snapshot of it', options && options.id);
      return get(changeset, key + '.id');
    },
    hasMany(key, options) {
      assert('Snapshot from changeset can only return the ids of a hasMany relationship, not an array of snapshots', options && options.ids);
      return changeset.get(key).map(e => e.get('id'));
    },
  };
}




Aucun commentaire:

Enregistrer un commentaire