Skip to content

Commit ab2d59f

Browse files
petehuntzpao
authored andcommitted
[addons] update() immutability helper
Dealing with immutable data is hard. This provides a simple helper (ported from the IG codebase) that makes dealing with immutable JSON data easier. Designed to be familiar for people who use MongoDB.
1 parent 61c287c commit ab2d59f

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* Copyright 2013-2014 Facebook, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* @emails react-core
17+
*/
18+
19+
"use strict";
20+
21+
var update = require('update');
22+
23+
describe('update', function() {
24+
it('should support push', function() {
25+
expect(update([1], {$push: [7]})).toEqual([1, 7]);
26+
expect(update.bind(null, [], {$push: 7})).toThrow(
27+
'Invariant Violation: update(): expected spec of $push to be an ' +
28+
'array; got 7. Did you forget to wrap your parameter in an array?'
29+
);
30+
expect(update.bind(null, 1, {$push: 7})).toThrow(
31+
'Invariant Violation: update(): expected target of $push to be an ' +
32+
'array; got 1.'
33+
);
34+
});
35+
36+
it('should support unshift', function() {
37+
expect(update([1], {$unshift: [7]})).toEqual([7, 1]);
38+
expect(update.bind(null, [], {$unshift: 7})).toThrow(
39+
'Invariant Violation: update(): expected spec of $unshift to be an ' +
40+
'array; got 7. Did you forget to wrap your parameter in an array?'
41+
);
42+
expect(update.bind(null, 1, {$unshift: 7})).toThrow(
43+
'Invariant Violation: update(): expected target of $unshift to be an ' +
44+
'array; got 1.'
45+
);
46+
});
47+
48+
it('should support splice', function() {
49+
expect(update([1, 4, 3], {$splice: [[1, 1, 2]]})).toEqual([1, 2, 3]);
50+
expect(update.bind(null, [], {$splice: 1})).toThrow(
51+
'Invariant Violation: update(): expected spec of $splice to be an ' +
52+
'array of arrays; got 1. Did you forget to wrap your parameters in an '+
53+
'array?'
54+
);
55+
expect(update.bind(null, [], {$splice: [1]})).toThrow(
56+
'Invariant Violation: update(): expected spec of $splice to be an ' +
57+
'array of arrays; got 1. Did you forget to wrap your parameters in an ' +
58+
'array?'
59+
);
60+
expect(update.bind(null, 1, {$splice: 7})).toThrow(
61+
'Invariant Violation: Expected $splice target to be an array; got 1'
62+
);
63+
});
64+
65+
it('should support merge', function() {
66+
expect(update({a: 'b'}, {$merge: {c: 'd'}})).toEqual({a: 'b', c: 'd'});
67+
expect(update.bind(null, {}, {$merge: 7})).toThrow(
68+
'Invariant Violation: update(): $merge expects a spec of type ' +
69+
'\'object\'; got 7'
70+
);
71+
expect(update.bind(null, 7, {$merge: {a: 'b'}})).toThrow(
72+
'Invariant Violation: update(): $merge expects a target of type ' +
73+
'\'object\'; got 7'
74+
);
75+
});
76+
77+
it('should support set', function() {
78+
expect(update({a: 'b'}, {$set: {c: 'd'}})).toEqual({c: 'd'});
79+
});
80+
81+
it('should support deep updates', function() {
82+
expect(update({a: 'b', c: {d: 'e'}}, {c: {d: {$set: 'f'}}})).toEqual({
83+
a: 'b',
84+
c: {d: 'f'}
85+
});
86+
});
87+
88+
it('should require a directive', function() {
89+
expect(update.bind(null, {a: 'b'}, {a: 'c'})).toThrow(
90+
'Invariant Violation: update(): You provided a key path to update() ' +
91+
'that did not contain one of $push, $unshift, $splice, $set, $merge. ' +
92+
'Did you forget to include {$set: ...}?'
93+
);
94+
});
95+
});

src/addons/update.js

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/**
2+
* Copyright 2013-2014 Facebook, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* @providesModule update
17+
*/
18+
19+
"use strict";
20+
21+
var copyProperties = require('copyProperties');
22+
var keyOf = require('keyOf');
23+
var invariant = require('invariant');
24+
25+
function shallowCopy(x) {
26+
if (Array.isArray(x)) {
27+
return x.concat();
28+
} else if (x && typeof x === 'object') {
29+
return copyProperties(new x.constructor(), x);
30+
} else {
31+
return x;
32+
}
33+
}
34+
35+
var DIRECTIVE_PUSH = keyOf({$push: null});
36+
var DIRECTIVE_UNSHIFT = keyOf({$unshift: null});
37+
var DIRECTIVE_SPLICE = keyOf({$splice: null});
38+
var DIRECTIVE_SET = keyOf({$set: null});
39+
var DIRECTIVE_MERGE = keyOf({$merge: null});
40+
41+
var ALL_DIRECTIVES_LIST = [
42+
DIRECTIVE_PUSH,
43+
DIRECTIVE_UNSHIFT,
44+
DIRECTIVE_SPLICE,
45+
DIRECTIVE_SET,
46+
DIRECTIVE_MERGE
47+
];
48+
49+
var ALL_DIRECTIVES_SET = {};
50+
51+
ALL_DIRECTIVES_LIST.forEach(function(directive) {
52+
ALL_DIRECTIVES_SET[directive] = true;
53+
});
54+
55+
function invariantArrayCase(value, spec, directive) {
56+
invariant(
57+
Array.isArray(value),
58+
'update(): expected target of %s to be an array; got %s.',
59+
directive,
60+
value
61+
);
62+
var specValue = spec[directive];
63+
invariant(
64+
Array.isArray(specValue),
65+
'update(): expected spec of %s to be an array; got %s. ' +
66+
'Did you forget to wrap your parameter in an array?',
67+
directive,
68+
specValue
69+
);
70+
}
71+
72+
function update(value, spec) {
73+
invariant(
74+
typeof spec === 'object',
75+
'update(): You provided a key path to update() that did not contain one ' +
76+
'of %s. Did you forget to include {%s: ...}?',
77+
ALL_DIRECTIVES_LIST.join(', '),
78+
DIRECTIVE_SET
79+
);
80+
81+
if (spec.hasOwnProperty(DIRECTIVE_SET)) {
82+
invariant(
83+
Object.keys(spec).length === 1,
84+
'Cannot have more than one key in an object with %s',
85+
DIRECTIVE_SET
86+
);
87+
88+
return spec[DIRECTIVE_SET];
89+
}
90+
91+
var nextValue = shallowCopy(value);
92+
93+
if (spec.hasOwnProperty(DIRECTIVE_MERGE)) {
94+
var mergeObj = spec[DIRECTIVE_MERGE];
95+
invariant(
96+
mergeObj && typeof mergeObj === 'object',
97+
'update(): %s expects a spec of type \'object\'; got %s',
98+
DIRECTIVE_MERGE,
99+
mergeObj
100+
);
101+
invariant(
102+
nextValue && typeof nextValue === 'object',
103+
'update(): %s expects a target of type \'object\'; got %s',
104+
DIRECTIVE_MERGE,
105+
nextValue
106+
);
107+
copyProperties(nextValue, spec[DIRECTIVE_MERGE]);
108+
}
109+
110+
if (spec.hasOwnProperty(DIRECTIVE_PUSH)) {
111+
invariantArrayCase(value, spec, DIRECTIVE_PUSH);
112+
spec[DIRECTIVE_PUSH].forEach(function(item) {
113+
nextValue.push(item);
114+
});
115+
}
116+
117+
if (spec.hasOwnProperty(DIRECTIVE_UNSHIFT)) {
118+
invariantArrayCase(value, spec, DIRECTIVE_UNSHIFT);
119+
spec[DIRECTIVE_UNSHIFT].forEach(function(item) {
120+
nextValue.unshift(item);
121+
});
122+
}
123+
124+
if (spec.hasOwnProperty(DIRECTIVE_SPLICE)) {
125+
invariant(
126+
Array.isArray(value),
127+
'Expected %s target to be an array; got %s',
128+
DIRECTIVE_SPLICE,
129+
value
130+
);
131+
invariant(
132+
Array.isArray(spec[DIRECTIVE_SPLICE]),
133+
'update(): expected spec of %s to be an array of arrays; got %s. ' +
134+
'Did you forget to wrap your parameters in an array?',
135+
DIRECTIVE_SPLICE,
136+
spec[DIRECTIVE_SPLICE]
137+
);
138+
spec[DIRECTIVE_SPLICE].forEach(function(args) {
139+
invariant(
140+
Array.isArray(args),
141+
'update(): expected spec of %s to be an array of arrays; got %s. ' +
142+
'Did you forget to wrap your parameters in an array?',
143+
DIRECTIVE_SPLICE,
144+
spec[DIRECTIVE_SPLICE]
145+
);
146+
nextValue.splice.apply(nextValue, args);
147+
});
148+
}
149+
150+
for (var k in spec) {
151+
if (!ALL_DIRECTIVES_SET[k]) {
152+
nextValue[k] = update(value[k], spec[k]);
153+
}
154+
}
155+
156+
return nextValue;
157+
}
158+
159+
module.exports = update;

0 commit comments

Comments
 (0)