Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for batch record creation #88

Merged
merged 2 commits into from
Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions lib/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var Table = Class.extend({
// Public API
this.find = callbackToPromise(this._findRecordById, this);
this.select = this._selectRecords.bind(this);
this.create = callbackToPromise(this._createRecord, this);
this.create = callbackToPromise(this._createRecords, this);
this.update = callbackToPromise(this._updateRecord, this);
this.destroy = callbackToPromise(this._destroyRecord, this);
this.replace = callbackToPromise(this._replaceRecord, this);
Expand Down Expand Up @@ -78,18 +78,37 @@ var Table = Class.extend({
_urlEncodedNameOrId: function(){
return this.id || encodeURIComponent(this.name);
},
_createRecord: function(recordData, optionalParameters, done) {
_createRecords: function(recordsData, optionalParameters, done) {
var that = this;
var isCreatingMultipleRecords = isArray(recordsData);

if (!done) {
done = optionalParameters;
optionalParameters = {};
}
var requestData = assign({fields: recordData}, optionalParameters);
var requestData;
if (isCreatingMultipleRecords) {
requestData = {
records: recordsData.map(function (fields) {
return {fields: fields};
}),
};
} else {
requestData = {fields: recordsData};
}
assign(requestData, optionalParameters);
this._base.runAction('post', '/' + that._urlEncodedNameOrId() + '/', {}, requestData, function(err, resp, body) {
if (err) { done(err); return; }

var record = new Record(that, body.id, body);
done(null, record);
var result;
if (isCreatingMultipleRecords) {
result = body.records.map(function (record) {
return new Record(that, record.id, record);
});
} else {
result = new Record(that, body.id, body);
}
done(null, result);
});
},
_updateRecord: function(recordId, recordData, opts, done) {
Expand Down
94 changes: 94 additions & 0 deletions test/create.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use strict';

var testHelpers = require('./test_helpers');

describe('record creation', function () {
var airtable;
var teardownAsync;

beforeAll(function () {
return testHelpers.getMockEnvironmentAsync().then(function (env) {
airtable = env.airtable;
teardownAsync = env.teardownAsync;
});
});

afterAll(function () {
return teardownAsync();
});

it('can create one record', function () {
return airtable
.base('app123')
.table('Table')
.create({
foo: 'boo',
bar: 'yar',
})
.then(function (createdRecord) {
expect(createdRecord.id).toBe('rec0');
expect(createdRecord.get('foo')).toBe('boo');
expect(createdRecord.get('bar')).toBe('yar');
});
});

it('can add the "typecast" parameter when creating one record', function () {
return airtable
.base('app123')
.table('Table')
.create({
foo: 'boo',
bar: 'yar',
}, {typecast: true})
.then(function (createdRecord) {
expect(createdRecord.id).toBe('rec0');
expect(createdRecord.get('typecasted')).toBe(true);
});
});

it('can create one record with an array', function () {
return airtable
.base('app123')
.table('Table')
.create([{foo: 'boo'}])
.then(function (createdRecords) {
expect(createdRecords).toHaveLength(1);
expect(createdRecords[0].id).toBe('rec0');
expect(createdRecords[0].get('foo')).toBe('boo');
});
});

it('can create two records', function () {
return airtable
.base('app123')
.table('Table')
.create([
{foo: 'boo'},
{bar: 'yar'},
])
.then(function (createdRecords) {
expect(createdRecords).toHaveLength(2);
expect(createdRecords[0].id).toBe('rec0');
expect(createdRecords[0].get('foo')).toBe('boo');
expect(createdRecords[1].id).toBe('rec1');
expect(createdRecords[1].get('bar')).toBe('yar');
});
});

it('can create two records with the "typecast" parameter', function () {
return airtable
.base('app123')
.table('Table')
.create([
{foo: 'boo'},
{bar: 'yar'},
], {typecast: true})
.then(function (createdRecords) {
expect(createdRecords).toHaveLength(2);
expect(createdRecords[0].id).toBe('rec0');
expect(createdRecords[0].get('typecasted')).toBe(true);
expect(createdRecords[1].id).toBe('rec1');
expect(createdRecords[1].get('typecasted')).toBe(true);
});
});
});
66 changes: 6 additions & 60 deletions test/delete.test.js
Original file line number Diff line number Diff line change
@@ -1,74 +1,20 @@
'use strict';

var Airtable = require('../lib/airtable');
var express = require('express');
var bodyParser = require('body-parser');
var getPort = require('get-port');
var util = require('util');
var testHelpers = require('./test_helpers');

describe('record deletion', function () {
var airtable;
var testServer;
var testServerPort;
var teardownAsync;

beforeAll(function () {
var app = express();

app.use(bodyParser.json());

function checkParamsMiddleware(req, res, next) {
var areParamsValid = (
(req.get('authorization') === 'Bearer key123') &&
(req.params.baseId === 'app123') &&
(req.params.tableIdOrName === 'Table')
);
if (areParamsValid) {
next();
} else {
next(new Error('Bad parameters'));
}
}

app.delete('/v0/:baseId/:tableIdOrName/:recordId', checkParamsMiddleware, function (req, res, next) {
res.json({
id: req.params.recordId,
deleted: true
});
});

app.delete('/v0/:baseId/:tableIdOrName', checkParamsMiddleware, function (req, res, next) {
res.json({
records: req.query.records.map(function (recordId) {
return {
id: recordId,
deleted: true
};
})
});
});

return getPort().then(function (port) {
testServerPort = port;

airtable = new Airtable({
apiKey: 'key123',
endpointUrl: 'http://localhost:' + testServerPort
});

return new Promise(function (resolve, reject) {
testServer = app.listen(testServerPort, function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
return testHelpers.getMockEnvironmentAsync().then(function (env) {
airtable = env.airtable;
teardownAsync = env.teardownAsync;
});
});

afterAll(function () {
return util.promisify(testServer.close.bind(testServer))();
return teardownAsync();
});

it('can delete one record', function () {
Expand Down
96 changes: 96 additions & 0 deletions test/test_helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
'use strict';

var Airtable = require('../lib/airtable');
var express = require('express');
var bodyParser = require('body-parser');
var getPort = require('get-port');
var util = require('util');

var FAKE_CREATED_TIME = '2020-04-20T16:20:00.000Z';

function getMockEnvironmentAsync() {
var app = express();

app.use(bodyParser.json());

app.post('/v0/:baseId/:tableIdOrName', _checkParamsMiddleware, function (req, res) {
var isCreatingJustOneRecord = !!req.body.fields;
var recordsInBody = isCreatingJustOneRecord ? [req.body] : req.body.records;

var records = recordsInBody.map(function (record, index) {
var fields = req.body.typecast ? {typecasted: true} : record.fields;
return {
id: 'rec' + index,
createdTime: FAKE_CREATED_TIME,
fields: fields,
};
});

var responseBody = isCreatingJustOneRecord ? records[0] : {records: records};
res.json(responseBody);
});

app.delete('/v0/:baseId/:tableIdOrName/:recordId', _checkParamsMiddleware, function (req, res, next) {
res.json({
id: req.params.recordId,
deleted: true
});
});

app.delete('/v0/:baseId/:tableIdOrName', _checkParamsMiddleware, function (req, res, next) {
res.json({
records: req.query.records.map(function (recordId) {
return {
id: recordId,
deleted: true
};
})
});
});

app.use(function (err, req, res, next) {
console.error(err);
res.status(500);
res.json({
error: {
type: 'TEST_ERROR',
message: err.message,
}
});
});

return getPort().then(function (testServerPort) {
return new Promise(function (resolve, reject) {
var testServer = app.listen(testServerPort, function (err) {
if (err) {
reject(err);
} else {
resolve({
airtable: new Airtable({
apiKey: 'key123',
endpointUrl: 'http://localhost:' + testServerPort,
}),
teardownAsync: util.promisify(testServer.close.bind(testServer)),
});
}
});
});
});
}

function _checkParamsMiddleware(req, res, next) {
var areParamsValid = (
(req.get('authorization') === 'Bearer key123') &&
(req.params.baseId === 'app123') &&
(req.params.tableIdOrName === 'Table')
);
if (areParamsValid) {
next();
} else {
next(new Error('Bad parameters'));
}
}

module.exports = {
getMockEnvironmentAsync: getMockEnvironmentAsync,
};