Skip to content

Commit c47e71a

Browse files
author
Lee Richmond
committed
Merge pull request #5 from wtandy/scope_copy
Return deep copy for scope modification methods
2 parents 7e64b78 + 316ae68 commit c47e71a

File tree

3 files changed

+76
-34
lines changed

3 files changed

+76
-34
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"dependencies": {
4646
"es6-promise": "^4.0.5",
4747
"isomorphic-fetch": "^2.2.1",
48+
"lodash.clonedeep": "4.5",
4849
"ts-loader": "^1.3.2"
4950
}
5051
}

src/scope.ts

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import IncludeDirective from './util/include-directive';
55
import { CollectionProxy, RecordProxy } from './proxies';
66
import Request from './request';
77
import colorize from './util/colorize';
8+
import * as cloneDeep from 'lodash.clonedeep';
89

910
export default class Scope {
1011
model: typeof Model;
@@ -42,66 +43,82 @@ export default class Scope {
4243
}
4344

4445
page(pageNumber : number) : Scope {
45-
this._pagination.number = pageNumber;
46-
return this;
46+
let copy = this.copy()
47+
48+
copy._pagination.number = pageNumber;
49+
return copy;
4750
}
4851

4952
per(size : number) : Scope {
50-
this._pagination.size = size;
51-
return this;
53+
let copy = this.copy()
54+
55+
copy._pagination.size = size;
56+
return copy;
5257
}
5358

5459
where(clause: Object) : Scope {
60+
let copy = this.copy()
61+
5562
for (let key in clause) {
56-
this._filter[key] = clause[key];
63+
copy._filter[key] = clause[key];
5764
}
58-
return this;
65+
return copy;
5966
}
6067

6168
stats(clause: Object) : Scope {
69+
let copy = this.copy()
70+
6271
for (let key in clause) {
63-
this._stats[key] = clause[key];
72+
copy._stats[key] = clause[key];
6473
}
65-
return this;
74+
return copy;
6675
}
6776

6877
order(clause: Object | string) : Scope {
78+
let copy = this.copy()
79+
6980
if (typeof clause == "object") {
7081
for (let key in clause) {
71-
this._sort[key] = clause[key];
82+
copy._sort[key] = clause[key];
7283
}
7384
} else {
74-
this._sort[clause] = 'asc';
85+
copy._sort[clause] = 'asc';
7586
}
7687

77-
return this;
88+
return copy;
7889
}
7990

8091
select(clause: Object) {
92+
let copy = this.copy()
93+
8194
for (let key in clause) {
82-
this._fields[key] = clause[key];
95+
copy._fields[key] = clause[key];
8396
}
8497

85-
return this;
98+
return copy;
8699
}
87100

88101
selectExtra(clause: Object) {
102+
let copy = this.copy()
103+
89104
for (let key in clause) {
90-
this._extra_fields[key] = clause[key];
105+
copy._extra_fields[key] = clause[key];
91106
}
92107

93-
return this;
108+
return copy;
94109
}
95110

96111
includes(clause: Object | string | Array<any>) : Scope {
112+
let copy = this.copy()
113+
97114
let directive = new IncludeDirective(clause);
98115
let directiveObject = directive.toObject();
99116

100117
for (let key in directiveObject) {
101-
this._include[key] = directiveObject[key];
118+
copy._include[key] = directiveObject[key];
102119
}
103120

104-
return this;
121+
return copy;
105122
}
106123

107124
// The `Model` class has a `scope()` method to return the scope for it.
@@ -133,6 +150,12 @@ export default class Scope {
133150
}
134151
}
135152

153+
copy() : Scope {
154+
let newScope = cloneDeep(this);
155+
156+
return newScope;
157+
}
158+
136159
// private
137160

138161
private _sortParam(clause: Object | void) {

test/unit/scope-test.ts

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ beforeEach(function() {
1313
describe('Scope', function() {
1414
describe('#page()', function() {
1515
it('sets correct pagination information', function() {
16-
scope.page(2);
16+
scope = scope.page(2);
1717
expect(scope._pagination).to.eql({ number: 2 });
1818
});
1919

@@ -24,7 +24,7 @@ describe('Scope', function() {
2424

2525
describe('#per()', function() {
2626
it('sets correct pagination information', function() {
27-
scope.per(10);
27+
scope = scope.per(10);
2828
expect(scope._pagination).to.eql({ size: 10 });
2929
});
3030

@@ -35,9 +35,9 @@ describe('Scope', function() {
3535

3636
describe('#where()', function() {
3737
it('updates filter criteria', function() {
38-
scope.where({ foo: 'bar' })
39-
scope.where({ bar: 'baz' })
40-
scope.where({ foo: 'bar2' })
38+
scope = scope.where({ foo: 'bar' })
39+
.where({ bar: 'baz' })
40+
.where({ foo: 'bar2' })
4141
expect(scope._filter).to.eql({
4242
foo: 'bar2',
4343
bar: 'baz'
@@ -51,8 +51,8 @@ describe('Scope', function() {
5151

5252
describe('#stats()', function() {
5353
it('updates stats request', function() {
54-
scope.stats({ total: 'count' });
55-
scope.stats({ average: 'cost' });
54+
scope = scope.stats({ total: 'count' })
55+
.stats({ average: 'cost' });
5656

5757
expect(scope._stats).to.eql({
5858
total: 'count',
@@ -67,8 +67,8 @@ describe('Scope', function() {
6767

6868
describe('#order()', function() {
6969
it('updates sort criteria', function() {
70-
scope.order('foo');
71-
scope.order({ bar: 'desc' });
70+
scope = scope.order('foo')
71+
.order({ bar: 'desc' });
7272
expect(scope._sort).to.eql({
7373
foo: 'asc',
7474
bar: 'desc'
@@ -82,8 +82,8 @@ describe('Scope', function() {
8282

8383
describe('#select()', function() {
8484
it('updates fields criteria', function() {
85-
scope.select({ people: ['foo', 'bar'] });
86-
scope.select({ things: ['baz'] })
85+
scope = scope.select({ people: ['foo', 'bar'] })
86+
.select({ things: ['baz'] })
8787
expect(scope._fields).to.eql({
8888
people: ['foo', 'bar'],
8989
things: ['baz']
@@ -98,7 +98,7 @@ describe('Scope', function() {
9898
describe('#includes()', function() {
9999
describe('when passed a string', function() {
100100
it('updates include criteria', function() {
101-
scope.includes('foo')
101+
scope = scope.includes('foo')
102102
expect(scope._include).to.eql({
103103
foo: {}
104104
});
@@ -107,7 +107,7 @@ describe('Scope', function() {
107107

108108
describe('when passed an array', function() {
109109
it('updates include criteria', function() {
110-
scope.includes(['foo', 'bar']);
110+
scope = scope.includes(['foo', 'bar']);
111111
expect(scope._include).to.eql({
112112
foo: {},
113113
bar: {}
@@ -117,7 +117,7 @@ describe('Scope', function() {
117117

118118
describe('when passed a nested object', function() {
119119
it('updates include criteria', function() {
120-
scope.includes({ a: ['b', { c: 'd' }] });
120+
scope = scope.includes({ a: ['b', { c: 'd' }] });
121121
expect(scope._include).to.eql({
122122
a: {
123123
b: {},
@@ -142,7 +142,7 @@ describe('Scope', function() {
142142

143143
describe('#asQueryParams()', function() {
144144
it('transforms all scoping criteria into a jsonapi-compatible query param object', function() {
145-
scope
145+
scope = scope
146146
.page(2)
147147
.per(10)
148148
.where({ foo: 'bar' })
@@ -183,7 +183,7 @@ describe('Scope', function() {
183183

184184
describe('#toQueryParams', function() {
185185
it('transforms nested query parameter object to query string', function() {
186-
scope
186+
scope = scope
187187
.page(2)
188188
.per(10)
189189
.where({ foo: 'bar' })
@@ -196,7 +196,7 @@ describe('Scope', function() {
196196
});
197197

198198
it('does not include empty objects', function() {
199-
scope.page(2);
199+
scope = scope.page(2);
200200
expect(scope.toQueryParams().match(/field/) === null).to.eq(true);
201201
});
202202

@@ -206,4 +206,22 @@ describe('Scope', function() {
206206
})
207207
});
208208
});
209+
210+
describe('#copy', function() {
211+
it('should make a copy of the scope', function() {
212+
expect(scope.copy()).not.to.eq(scope)
213+
})
214+
215+
it('should make a copy of scope attributes', function() {
216+
let original = scope.order({foo: 'asc'}).page(1).per(20)
217+
218+
let copy = original.copy()
219+
220+
expect(original._pagination).not.to.eq(copy._pagination)
221+
expect(original._pagination).to.deep.eq(copy._pagination)
222+
223+
expect(original._sort).not.to.eq(copy._sort)
224+
expect(original._sort).to.deep.eq(copy._sort)
225+
})
226+
})
209227
});

0 commit comments

Comments
 (0)