Skip to content

Commit

Permalink
Allow merging invalid changesets (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanvanderbyl authored and poteto committed Jul 22, 2016
1 parent 41a5878 commit 85337fc
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 9 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ changeset.rollback(); // returns changeset

#### `merge`

Merges 2 valid changesets and returns a new changeset with the same underlying content and validator as the origin. Both changesets must point to the same underlying object. For example:
Merges 2 changesets and returns a new changeset with the same underlying content and validator as the origin. Both changesets must point to the same underlying object. For example:

```js
let changesetA = new Changeset(user, validatorFn);
Expand Down
13 changes: 10 additions & 3 deletions addon/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import isEmptyObject from 'ember-changeset/utils/computed/is-empty-object';
import isPromise from 'ember-changeset/utils/is-promise';
import isObject from 'ember-changeset/utils/is-object';
import pureAssign from 'ember-changeset/utils/assign';
import objectWithout from 'ember-changeset/utils/object-without';
import { CHANGESET, isChangeset } from 'ember-changeset/-private/internals';

const {
Expand Down Expand Up @@ -211,18 +212,24 @@ export function changeset(obj, validateFn = defaultValidatorFn, validationMap =
let content = get(this, CONTENT);
assert('Cannot merge with a non-changeset', isChangeset(changeset));
assert('Cannot merge with a changeset of different content', get(changeset, CONTENT) === content);
assert('Cannot merge invalid changesets', get(this, 'isValid') && get(changeset, 'isValid'));

if (get(this, 'isPristine') && get(changeset, 'isPristine')) {
return this;
}

let changesA = get(this, CHANGES);
let changesB = get(changeset, CHANGES);
let mergedChanges = pureAssign(changesA, changesB);
let errorsA = get(this, ERRORS);
let errorsB = get(changeset, ERRORS);
let newChangeset = new Changeset(content, get(this, VALIDATOR));
let newErrors = objectWithout(keys(changesB), errorsA);
let newChanges = objectWithout(keys(errorsB), changesA);
let mergedChanges = pureAssign(newChanges, changesB);
let mergedErrors = pureAssign(newErrors, errorsB);

newChangeset[CHANGES] = mergedChanges;
newChangeset.notifyPropertyChange(CHANGES);
newChangeset[ERRORS] = mergedErrors;
newChangeset._notifyVirtualProperties();

return newChangeset;
},
Expand Down
18 changes: 18 additions & 0 deletions addon/utils/object-without.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const { keys } = Object;

/**
* Merges all sources together, excluding keys in excludedKeys.
*
* @param {Array[String]} excludedKeys
* @param {...Object} sources
*
* @return {Object}
*/
export default function objectWithout(excludedKeys, ...sources) {
return sources.reduce((acc, source) => {
keys(source)
.filter((key) => excludedKeys.indexOf(key) === -1 || !source.hasOwnProperty(key))
.forEach((key) => acc[key] = source[key]);
return acc;
}, {});
}
1 change: 1 addition & 0 deletions app/utils/object-without.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-changeset/utils/object-without';
22 changes: 17 additions & 5 deletions tests/unit/changeset-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,27 @@ test('#merge merges 2 valid changesets', function(assert) {
assert.deepEqual(get(dummyChangesetB, 'changes'), [{ key: 'lastName', value: 'Bob' }], 'should not mutate second changeset');
});

test('#merge does not merge invalid changesets', function(assert) {
test('#merge merges invalid changesets', function(assert) {
let dummyChangesetA = new Changeset(dummyModel, dummyValidator);
let dummyChangesetB = new Changeset(dummyModel, dummyValidator);
let dummyChangesetC = new Changeset(dummyModel, dummyValidator);
dummyChangesetA.set('age', 21);
dummyChangesetA.set('name', 'a');
dummyChangesetB.set('name', 'b');
dummyChangesetB.set('name', 'Tony Stark');
dummyChangesetC.set('name', 'b');

assert.throws(() => dummyChangesetA.merge(dummyChangesetB), ({ message }) => {
return message === 'Assertion Failed: Cannot merge invalid changesets';
}, 'should throw error');
let dummyChangesetD = dummyChangesetA.merge(dummyChangesetB);
dummyChangesetD = dummyChangesetD.merge(dummyChangesetC);

let expectedChanges = [{ key: 'age', value: 21 }];
let expectedErrors = [{ key: 'name', 'validation': 'too short', value: 'b' }];

assert.deepEqual(get(dummyChangesetA, 'isInvalid'), true, 'changesetA is not valid becuase of name');
assert.deepEqual(get(dummyChangesetB, 'isValid'), true, 'changesetB should be invalid');
assert.deepEqual(get(dummyChangesetC, 'isInvalid'), true, 'changesetC should be invalid');
assert.deepEqual(get(dummyChangesetD, 'isInvalid'), true, 'changesetD should be invalid');
assert.deepEqual(get(dummyChangesetD, 'changes'), expectedChanges, 'should not merge invalid changes');
assert.deepEqual(get(dummyChangesetD, 'errors'), expectedErrors, 'should assign errors from both changesets');
});

test('#merge does not merge a changeset with a non-changeset', function(assert) {
Expand Down
15 changes: 15 additions & 0 deletions tests/unit/utils/object-without-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import objectWithout from 'dummy/utils/object-without';
import { module, test } from 'qunit';

module('Unit | Utility | object without');

test('it exludes the given keys from all merged objects', function(assert) {
let objA = { name: 'Ivan' };
let objB = { name: 'John' };
let objC = { age: 27 };
let objD = objectWithout([ 'age' ], objA, objB, objC);

assert.deepEqual(objD, { name: 'John' }, 'result only contains name');
assert.deepEqual(objA.name, 'Ivan', 'does not mutate original object');
assert.deepEqual(objC.age, 27, 'does not mutate original object');
});

0 comments on commit 85337fc

Please sign in to comment.