Skip to content

Commit

Permalink
Merge pull request #149 from michielbdejong/148-collection-configure
Browse files Browse the repository at this point in the history
Fixes #148 - Extra arg on Kinto#collection to replace Collection#use.
  • Loading branch information
michielbdejong committed Sep 14, 2015
2 parents 26888cf + 3468b0e commit 2775503
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 54 deletions.
31 changes: 21 additions & 10 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ Transformers are basically hooks for encoding and decoding records, which can wo

### Remote transformers

Remote transformers aim at encoding records before pushing them to the remote server, and decoding them back when pulling changes. Remote transformers are registered by calling the `Collection#use()` method, which accepts a `Kinto.transformers.RemoteTransformer`-derived object instance:
Remote transformers aim at encoding records before pushing them to the remote server, and decoding them back when pulling changes. Remote transformers are registered through the optional second argument of `Kinto#collection()`, which accepts `Kinto.transformers.RemoteTransformer`-derived object instances in its `remoteTransformers` array.

```js
import Kinto from "kinto";
Expand All @@ -432,8 +432,9 @@ class MyRemoteTransformer extends Kinto.transformers.RemoteTransformer {
}

const kinto = new Kinto({remote: "https://my.server.tld/v1"});
coll = kinto.collection("articles");
coll.use(new MyRemoteTransformer());
coll = kinto.collection("articles", {
remoteTransformers: [ new MyRemoteTransformer() ]
});
```

> #### Notes
Expand Down Expand Up @@ -482,12 +483,14 @@ class MyAsyncRemoteTransformer extends Kinto.transformers.RemoteTransformer {
}
}

coll.use(new MyAsyncRemoteTransformer());
coll = kinto.collection("articles", {
remoteTransformers: [ new MyAsyncRemoteTransformer() ]
});
```

### Multiple transformers

Transformers are stacked when `#use()` is called multiple times, in the order of the calls; that means you can chain multiple encoding operations, with the decoding ones being processed in the reverse order:
The remoteTransformers field of the options object passed to `Kinto#collection` is an Array. That means you can chain multiple encoding operations, with the decoding ones being processed in the reverse order:

```js
class TitleCharTransformer extends Kinto.transformers.RemoteTransformer {
Expand All @@ -505,8 +508,12 @@ class TitleCharTransformer extends Kinto.transformers.RemoteTransformer {
}
}

coll.use(new TitleCharTransformer("!"));
coll.use(new TitleCharTransformer("?"));
coll = kinto.collection("articles", {
remoteTransformers: [
new TitleCharTransformer("!"),
new TitleCharTransformer("?")
]
});

coll.create({title: "foo"}).then(_ => coll.sync())
// remotely saved:
Expand Down Expand Up @@ -534,8 +541,12 @@ var TitleCharTransformer = Kinto.createRemoteTransformer({
}
});

coll.use(new TitleCharTransformer("!"));
coll.use(new TitleCharTransformer("?"));
coll = kinto.collection("articles", {
remoteTransformers: [
new TitleCharTransformer("!"),
new TitleCharTransformer("?")
]
});
```

> #### Notes
Expand All @@ -544,6 +555,6 @@ coll.use(new TitleCharTransformer("?"));
### Limitations

There's currently no way to deal with adding tranformers to an already filled remote database; that would mean remote data migrations, and both Kinto and Kinto.js don't provide this feature just yet.
There's currently no way to deal with adding transformers to an already filled remote database; that would mean remote data migrations, and both Kinto and Kinto.js don't provide this feature just yet.

**As a rule of thumb, you should only start using transformers on an empty remote collection.**
17 changes: 7 additions & 10 deletions src/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default class Collection {
this._bucket = bucket;
this._name = name;
this._lastModified = null;
this._transformers = {remote: []};

const DBAdapter = options.adapter || IDB;
const dbPrefix = options.dbPrefix || "";
const db = new DBAdapter(`${dbPrefix}${bucket}/${name}`);
Expand All @@ -71,6 +71,7 @@ export default class Collection {
this.db = db;
this.api = api;
this.events = options.events || new EventEmitter();
this.remoteTransformers = options.remoteTransformers || [];
}

get name() {
Expand Down Expand Up @@ -113,10 +114,6 @@ export default class Collection {
});
}

use(transfomer) {
this._transformers[transfomer.type].push(transfomer);
}

/**
* Encodes a record.
*
Expand All @@ -125,11 +122,11 @@ export default class Collection {
* @return {Promise}
*/
_encodeRecord(type, record) {
if (!this._transformers[type].length) {
if (!this[`${type}Transformers`].length) {
return Promise.resolve(record);
}
return waterfall(this._transformers[type].map(transfomer => {
return record => transfomer.encode(record);
return waterfall(this[`${type}Transformers`].map(transformer => {
return record => transformer.encode(record);
}), record);
}

Expand All @@ -141,10 +138,10 @@ export default class Collection {
* @return {Promise}
*/
_decodeRecord(type, record) {
if (!this._transformers[type].length) {
if (!this[`${type}Transformers`].length) {
return Promise.resolve(record);
}
return waterfall(this._transformers[type].reverse().map(transformer => {
return waterfall(this[`${type}Transformers`].reverse().map(transformer => {
return record => transformer.decode(record);
}), record);
}
Expand Down
26 changes: 12 additions & 14 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,20 @@ export default class Kinto {
remote: DEFAULT_REMOTE,
};
this._options = Object.assign(defaults, options);
this._collections = {};
// public properties
this.events = this._options.events;
}

/**
* Creates or retrieve a Collection instance.
* Creates a Collection instance. The second (optional) parameter
* will set collection-level options like e.g. remoteTransformers.
*
* @param {String} collName The collection name.
* @param {Object} options May contain the following fields:
* remoteTransformers: Array of RemoteTransformers
* @return {Collection}
*/
collection(collName) {
collection(collName, options = {}) {
if (!collName) {
throw new Error("missing collection name");
}
Expand All @@ -124,16 +126,12 @@ export default class Kinto {
events: this._options.events,
requestMode: this._options.requestMode,
});

if (!this._collections.hasOwnProperty(collName)) {
const bucket = this._options.bucket;
this._collections[collName] = new Collection(bucket, collName, api, {
events: this._options.events,
adapter: this._options.adapter,
dbPrefix: this._options.dbPrefix,
});
}

return this._collections[collName];
const bucket = this._options.bucket;
return new Collection(bucket, collName, api, {
events: this._options.events,
adapter: this._options.adapter,
dbPrefix: this._options.dbPrefix,
remoteTransformers: options.remoteTransformers
});
}
}
41 changes: 31 additions & 10 deletions test/collection_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ const TEST_COLLECTION_NAME = "kinto-test";
const FAKE_SERVER_URL = "http://fake-server/v1";

describe("Collection", () => {
var sandbox, events, api;
var sandbox, events, remoteTransformers, api;
const article = {title: "foo", url: "http://foo"};

function testCollection() {
function testCollection(options={}) {
events = new EventEmitter();
remoteTransformers = options.remoteTransformers;
api = new Api(FAKE_SERVER_URL, {events});
return new Collection(TEST_BUCKET_NAME, TEST_COLLECTION_NAME, api, {events});
return new Collection(TEST_BUCKET_NAME, TEST_COLLECTION_NAME, api, {
events,
remoteTransformers
});
}

class QuestionMarkTransformer extends RemoteTransformer {
Expand Down Expand Up @@ -570,8 +574,12 @@ describe("Collection", () => {

describe("transformers", () => {
it("should asynchronously encode records", () => {
articles.use(new QuestionMarkTransformer());
articles.use(new ExclamationMarkTransformer());
articles = testCollection({
remoteTransformers: [
new QuestionMarkTransformer(),
new ExclamationMarkTransformer()
]
});

return articles.gatherLocalChanges()
.then(res => res.toSync.map(r => r.title))
Expand Down Expand Up @@ -801,16 +809,24 @@ describe("Collection", () => {
});

it("should decode incoming encoded records using a single transformer", () => {
articles.use(new CharDecodeTransformer("#"));
articles = testCollection({
remoteTransformers: [
new CharDecodeTransformer("#")
]
});

return articles.importChanges(result, {changes: [{id: uuid4(), title: "bar"}]})
.then(res => res.created[0].title)
.should.become("bar#");
});

it("should decode incoming encoded records using multiple transformers", () => {
articles.use(new CharDecodeTransformer("!"));
articles.use(new CharDecodeTransformer("?"));
articles = testCollection({
remoteTransformers: [
new CharDecodeTransformer("!"),
new CharDecodeTransformer("?")
]
});

return articles.importChanges(result, {changes: [{id: uuid4(), title: "bar"}]})
.then(res => res.created[0].title)
Expand Down Expand Up @@ -850,8 +866,13 @@ describe("Collection", () => {
});

it("should batch send encoded records", () => {
articles.use(new QuestionMarkTransformer());
articles.use(new ExclamationMarkTransformer());
articles = testCollection({
remoteTransformers: [
new QuestionMarkTransformer(),
new ExclamationMarkTransformer()
]
});

const batch = sandbox.stub(articles.api, "batch").returns(Promise.resolve({
published: [],
errors: [],
Expand Down
23 changes: 18 additions & 5 deletions test/index_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,6 @@ describe("Kinto", () => {
expect(coll.bucket).eql("default");
});

it("should cache collection instance", () => {
const db = new Kinto();
expect(db.collection("a") == db.collection("a")).eql(true);
});

it("should reject on missing collection name", () => {
expect(() => new Kinto().collection())
.to.Throw(Error, /missing collection name/);
Expand Down Expand Up @@ -202,5 +197,23 @@ describe("Kinto", () => {

expect(coll.db).to.be.an.instanceOf(MyAdapter);
});

it("should make the collection's remoteTransformers default to []", () => {
const db = new Kinto();
const coll = db.collection("plop");

expect(coll.remoteTransformers).to.deep.equal([]);
});

it("should set collection's remoteTransformers", () => {
const MyRemoteTransformer = class extends RemoteTransformer {};
const db = new Kinto();
const options = {
remoteTransformers: [ new MyRemoteTransformer() ]
};
const coll = db.collection("plop", options);

expect(coll.remoteTransformers).to.deep.equal(options.remoteTransformers);
});
});
});
15 changes: 10 additions & 5 deletions test/integration_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const PSERVE_EXECUTABLE = process.env.KINTO_PSERVE_EXECUTABLE || "pserve";
const KINTO_CONFIG = __dirname + "/kinto.ini";

describe("Integration tests", () => {
var sandbox, server, tasks;
var sandbox, server, kinto, tasks;
const MAX_ATTEMPTS = 50;

function startServer(env) {
Expand Down Expand Up @@ -65,10 +65,11 @@ describe("Integration tests", () => {

sandbox = sinon.sandbox.create();

tasks = new Kinto({
kinto = new Kinto({
remote: TEST_KINTO_SERVER,
headers: {Authorization: "Basic " + btoa("user:pass")}
}).collection("tasks");
});
tasks = kinto.collection("tasks");

return tasks.clear().then(_ => flushServer());
});
Expand Down Expand Up @@ -344,8 +345,12 @@ describe("Integration tests", () => {
function testTransformer(name, Transformer) {
describe(name, () => {
beforeEach(() => {
tasks.use(new Transformer("!"));
tasks.use(new Transformer("?"));
tasks = kinto.collection("tasks", {
remoteTransformers: [
new Transformer("!"),
new Transformer("?")
]
});

return Promise.all([
tasks.create({id: uuid4(), title: "abc"}),
Expand Down
19 changes: 19 additions & 0 deletions test/transformers/remote_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use strict";

import { expect } from "chai";

import RemoteTransformer from "../../src/transformers/remote";

describe("transformers.RemoteTransformer", () => {
var transformer;
beforeEach(() => transformer = new RemoteTransformer());

it("should have type remote", () => {
expect(transformer.type).to.equal("remote");
});

it("should throw for non-implemented methods", () => {
expect(() => transformer.encode()).to.Throw(Error, "Not implemented.");
expect(() => transformer.decode()).to.Throw(Error, "Not implemented.");
});
});

0 comments on commit 2775503

Please sign in to comment.