Skip to content

Commit

Permalink
frint-data: Collections made immutable (#361)
Browse files Browse the repository at this point in the history
* tests.

* frint-data: mutations in Collections via custom methods only.

* frint-data: update tests.

* frint-data: docs updated.

* frint-data: typo.
  • Loading branch information
fahad19 authored Nov 17, 2017
1 parent 48f17fb commit 54209ab
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 43 deletions.
40 changes: 35 additions & 5 deletions packages/frint-data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ const Todo = createModel({
// a group of Todo models can be put in a Todos collection
const Todos = createCollection({
model: Todo,

addTodo(todo) {
return this.push(todo);
},

extractLast() {
return this.pop();
},
});
```

Expand All @@ -108,7 +116,7 @@ const todo = new Todo({

// collection
const todos = new Todos();
todos.push(todo);
todos.addTodo(todo);
```

### Model usage
Expand All @@ -129,19 +137,20 @@ console.log(todo.title); // `First task [updated]`
### Collection usage

```js
// lets push the model to collection
todos.push(todo);
// lets add the model to collection
todos.addTodo(todo);
console.log(todos.length); // `1`

todos.push(new Todo({
todos.addTodo(new Todo({
title: 'My second task',
completed: false
}));
console.log(todos.length); // `2`

// let's take the last model out of the collection
const lastTodo = todos.pop();
const lastTodo = todos.extractLast();
console.log(lastTodo); // `My second task`
console.log(todos.length); // `1`
```

### Observing Models and Collections
Expand Down Expand Up @@ -342,6 +351,27 @@ const Todos = createCollection({

Collection instances also come with built-in methods like `map`, `filter`, `reduce` just like `Array`. See more in API Reference.

### Immutable collections

Collections are immutable by default. If you want to use built-in methods that mutate the collection, then you have to do them by defining custom methods first:

```js
const Todos = createCollection({
model: Todo,

addTodo(todo) {
// `push` and other mutating methods are only available inside custom methods
return this.push(todo);
},
});

const todos = new Todos();
todos.addTodo(new Todo({ title: 'First task' })); // works

// this will NOT work
todos.push(new Todo({ title: 'Another task' }));
```

## Embedding

Models can embed other Models and Collections, and this can go as many levels deep as the data structure demands.
Expand Down
43 changes: 32 additions & 11 deletions packages/frint-data/src/createCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export default function createCollection(options = {}) {
}
});

const mutableMethods = {};

/**
* Built-in methods
*/
Expand All @@ -68,7 +70,7 @@ export default function createCollection(options = {}) {
};
makeMethodReactive(this, 'at');

this.push = function (model) {
mutableMethods.push = function (model) {
if (!isModel(model)) {
throw new CollectionError('not a valid Model instance is being pushed');
}
Expand Down Expand Up @@ -101,7 +103,7 @@ export default function createCollection(options = {}) {

return result;
};
makeMethodReactive(this, 'push');
makeMethodReactive(mutableMethods, 'push');

// native array methods
[
Expand Down Expand Up @@ -140,7 +142,7 @@ export default function createCollection(options = {}) {
makeMethodReactive(this, lodashFuncName);
});

this.pop = function () {
mutableMethods.pop = function () {
const model = models.pop();

this._trigger('change');
Expand All @@ -150,7 +152,7 @@ export default function createCollection(options = {}) {
return model;
};

this.shift = function () {
mutableMethods.shift = function () {
const model = models.shift();

this._trigger('change');
Expand All @@ -160,7 +162,7 @@ export default function createCollection(options = {}) {
return model;
};

this.unshift = function (model) {
mutableMethods.unshift = function (model) {
if (!isModel(model)) {
throw new CollectionError('not a valid Model instance is being pushed');
}
Expand All @@ -187,13 +189,13 @@ export default function createCollection(options = {}) {
return result;
};

this.remove = function (model) {
mutableMethods.remove = function (model) {
const index = this.findIndex(model);

this.removeFrom(index);
};

this.removeFrom = function (index) {
mutableMethods.removeFrom = function (index) {
const model = models[index];

if (!model) {
Expand Down Expand Up @@ -224,29 +226,48 @@ export default function createCollection(options = {}) {
// listen$()
addListenerMethod(this, 'collection');

// combined context
const combinedContext = {
_on: this._on,
_off: this._off,
_trigger: this._trigger,
};

Object.keys(this).forEach((k) => {
combinedContext[k] = this[k];
});

Object.keys(mutableMethods).forEach((k) => {
combinedContext[k] = mutableMethods[k];
});

Object.keys(combinedContext).forEach((k) => {
combinedContext[k] = combinedContext[k].bind(combinedContext);
});

// methods
each(methods, (methodFunc, methodName) => {
if (typeof this[methodName] !== 'undefined') {
throw new MethodError(`conflicting method name: ${methodName}`);
}

this[methodName] = methodFunc.bind(this);
this[methodName] = methodFunc.bind(combinedContext);
});

// initialize
givenModels.forEach((v) => {
if (isModel(v)) {
this.push(v);
combinedContext.push(v);

return;
}

const model = new Model(v);
this.push(model);
combinedContext.push(model);
});

if (typeof options.initialize === 'function') {
options.initialize.bind(this)();
options.initialize.bind(combinedContext)();
}
}
}
Expand Down
Loading

0 comments on commit 54209ab

Please sign in to comment.