Listing records with Airtable API - javascript

I have an Airtable base that I can retrieve records from (see code below), but I'd like to get the value for other fields besides just "Location". Using "console.log('Retrieved: ', record.get('Location'));", how do I modify this line to include in the output the field values for a field called "Size" in addition to the "Location" field? I tried "console.log('Retrieved: ', record.get('Location', 'Size'));", but that didn't work.
Here's an excerpt from my code:
// Lists 3 records in Bins
base('Bins').select({
// Selecting the first 3 records in Grid view:
maxRecords: 3,
view: "Grid view"
}).eachPage(function page(records, fetchNextPage) {
// This function (`page`) will get called for each page of records.
records.forEach(function(record) {
console.log('Retrieved: ', record.get('Location'));
});
// To fetch the next page of records, call `fetchNextPage`.
// If there are more records, `page` will get called again.
// If there are no more records, `done` will get called.
fetchNextPage();
}, function done(err) {
if (err) { console.error(err); return; }
});
OUTPUT
Retrieved 170000118
Retrieved 170000119
Retrieved 170000120

I found this repo to help in when I tried to product situations like this.
A wrapper for common functions for accessing data on an airtable.com database. All queries return promises.
Here is how it works if you want to avoid using an npm package. But ultimatly the jist of it is to either use request or some short of promise fulfillment menthod to retrive the Records.
import Airtable from 'airtable'
import _ from 'lodash'
const ENDPOINT_URL = 'https://api.airtable.com'
let API_KEY // Can only set the API key once per program
export default class AirTable {
constructor({apiKey, databaseRef}) {
if(!API_KEY) {
API_KEY = apiKey
Airtable.configure({
endpointUrl: ENDPOINT_URL,
apiKey: API_KEY
});
}
this.base = Airtable.base(databaseRef)
this.get = {
single: this.getSingleRecordFrom.bind(this),
all: this.getAllRecordsFrom.bind(this),
match: this.getAllMatchedRecordsFrom.bind(this),
select: this.getRecordsSelect.bind(this)
}
this.insert = this.createRecord.bind(this)
this.add = this.insert
this.create = this.insert
this.update = this.updateRecord.bind(this)
this.set = this.update
this.remove = this.deleteRecord.bind(this)
this.delete = this.remove
this.destroy = this.remove
this.rem = this.remove
}
async createRecord({tableName, data}) {
return new Promise((resolve, reject) => {
this.base(tableName).create(data, (err, record) => {
if (err) {
console.error(err)
reject()
return
}
console.log("Created " + record.getId())
resolve(record)
})
})
}
async updateRecord({tableName, id, data}) {
return new Promise((resolve, reject) => {
this.base(tableName).update(id, data, (err, record) => {
if (err) {
console.error(err)
reject()
return
}
console.log("Updated " + record.getId())
resolve(record)
})
})
}
async deleteRecord({tableName, id, data}) {
return new Promise((resolve, reject) => {
this.base(tableName).destroy(id, (err, record) => {
if (err) {
console.error(err)
reject()
return
}
console.log("Deleted " + record.getId())
resolve(record)
})
})
}
async getSingleRecordFrom({tableName, id}) {
console.log(tableName, id)
return new Promise((resolve, reject) => {
this.base(tableName).find(id, function(err, record) {
if (err) {
console.error(err)
reject(err)
}
resolve(record)
})
// console.log(record);
})
}
async getAllRecordsFrom(tableName) {
return this.getRecordsSelect({tableName, select: {} })
}
async getAllMatchedRecordsFrom({tableName, column, value}) {
return this.getRecordsSelect({tableName, select: {filterByFormula:`${column} = ${value}`} }) // TODO: validate input
}
async getRecordsSelect({tableName, select}) {
return new Promise((resolve, reject) => {
let out = []
this.base(tableName).select(select).eachPage((records, fetchNextPage) => {
// Flatten single entry arrays, need to remove this hacky shit.
_.map(records, r => {
_.forOwn(r.fields, (value, key) => { // If array is single
if(_.isArray(value) && value.length == 1 && key != 'rooms') {
r.fields[key] = value[0]
}
});
})
out = _.concat(out, records)
fetchNextPage();
}, (err) => {
if (err) {
console.error(err)
reject(err)
} else {
// console.log(JSON.stringify(out, null, 4))
// console.log("HI")
resolve(out)
}
})
})
}
}
Hope this Makes sense, Also trying to make an API-Proxy fetching a whole table or even use Express to fetch record id's as arrays can work as well

You can use this code line.
records.forEach(function(record) {
console.log('Retrieved: ', record.get('Location') + ' ' + record.get('Size'));
});

Related

My promise is being resolved before my second query is able to run inside of a .map function

const getAdditionalInfosPromise = (ProgramID, LeadId) => {
return new Promise((resolve, reject) => {
connection.query(`SELECT * FROM crm_additionalLeadInfoFields WHERE ProgramID= ${ProgramID};`, (error, response) => {
if (error) reject(console.error(error, 'SQL ERROR IN GETADDITIONALINFOPROMISE'));
else {
let getAdditionalInfosData = [];
const getAdditionalInfos = response;
getAdditionalInfos.map((data, i) => {
​
let tableNameData;
// checking to see what our table name is
if (data.TableName === 'leads') {
tableNameData = `WHERE id= ${LeadId};`
}
else {
tableNameData = `Where LeadId = ${LeadId};`
}
​
// Remove any white spaces
​
const columnName = data.ColumnName.replace(/ +/g, "")
​
connection.query(`SELECT ${columnName} as pertinentValue
FROM ${data.TableName}
${tableNameData}`, (err, res) => {
if (err) console.error(err, 'MY SQL ERR IN GETADDITIONALINFOSPROMISE');
else {
console.log(data);
if (data.DisplayName !== 'Dealer Name') {
const pertinentValue = res[0].pertinentValue
​
getAdditionalInfosData.push({
'id': `additionalItem${i}`,
'label': data.DisplayName,
'value': `${pertinentValue !== null ? pertinentValue : ''}`,
'class': ''
})
​
}
}
})
})
resolve(getAdditionalInfosData)
}
})
​
})
}
Any Idea how to make this asynchronous? I tried using the async npm package but was having issue with getting any type of response back from the async.map(array, function (result,callback) {// Was null here} ). As it is right now it is returning an empty object and then logging my data afterwards Thanks to all who help! :)
Have you tried converting the items in the array you're mapping into promises? Then, using something like Promise.all (see below)? I also went ahead and moved a few items around to make it easier for me to read.
const getAdditionalInfosPromise = (ProgramID, LeadId) => {
return new Promise((resolve, reject) => {
connection.query(`SELECT * FROM crm_additionalLeadInfoFields WHERE ProgramID= ${ProgramID};`, (error, response) => {
if (error) reject(console.error(error, 'SQL ERROR IN GETADDITIONALINFOPROMISE'));
let getAdditionalInfosData = [];
const getAdditionalInfos = response;
const allPromises = getAdditionalInfos.map((data, i) => {
if (data.DisplayName !== 'Dealer Name') {
const tableNameData = data.TableName === 'leads' ? `WHERE id= ${LeadId};` : `Where LeadId = ${LeadId};`;
const columnName = data.ColumnName.replace(/ +/g, "")​
return new Promise((resolve, reject) => {
connection.query(`SELECT ${columnName} as pertinentValue FROM ${data.TableName} ${tableNameData}`, (err, res) => {
if (err) console.error(err, 'MY SQL ERR IN GETADDITIONALINFOSPROMISE');
console.log('within the select query', data);
const pertinentValue = res[0].pertinentValue​
resolve({
'id': `additionalItem${i}`,
'label': data.DisplayName,
'value': `${pertinentValue !== null ? pertinentValue : ''}`,
'class': ''
})​
})
})
}
reject({});
});
console.log(allPromises)
resolve(getAdditionalInfosData)
})​
})
}
Then you can do something like:
Promise.all(allPromises).then(function(values) {
console.log(values);
// do anything you need with your data here
});

Sqlite wait for database to open

I recently started to use javascript and electron. I want to use sqlite as a database technology. My problem is when I call:
OpenDB(dbPath);
CreateTable("sampleTable", "Column1 TEXT NOT NULL, Column2 TEXT NOT NULL");
Program actually calls CreateTable function without waiting database to open. If I call these two functions with delay program works as intended. I wrote function definitions below:
export function OpenDB(dbPath) {
projectDB = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error(err.message)
this.result = false;
return;
}
console.log('SQlite project database created.');
});
}
export function CreateTable(tableName, tableColumns) {
configDB.run('CREATE TABLE IF NOT EXISTS ' + tableName + ' (' + tableColumns + ')' , (err) => {
if(err) {
console.error(err);
console.log("Table couldn't created.")
return;
}
console.log("Table created.")
})
}
So my question is how can I make CreateTable function wait until the database actually opened?
From what I understand from my readings I need to create callback function but I couldn't manage to do it successfully. And more I read more I confused. Thanks in advance.
let db;
export function OpenDB(dbPath, cb) {
db = new sqlite3.Database(dbPath, cb);
}
export function CreateTable(tableName, tableColumns, cb) {
db.run('CREATE TABLE IF NOT EXISTS ' + tableName + ' (' + tableColumns + ')', cb);
}
And welcome to callback hell
OpenDb(dpPath, function (err) {
if (err)
return console.error(err.message);
CreateTable("sampleTable", "Column1 TEXT NOT NULL, Column2 TEXT NOT NULL", function (err) {
if (err)
return console.error(err);
});
})
If this way is inappropriate then use promisification
let db;
export function OpenDB(dbPath) {
return new Promise(function (resolve, reject) {
db = new sqlite3.Database(dbPath, err => err ? resolve() : reject(err));
});
}
export function CreateTable(tableName, tableColumns) {
return new Promise(function (resolve, reject) {
db.run('CREATE TABLE IF NOT EXISTS ' + tableName + ' (' + tableColumns + ')',
err => err ? resolve() : reject(err));
});
}
And usage
OpenDb(dbPath)
.then(() => CreateTable("sampleTable", "Column1 TEXT NOT NULL, Column2 TEXT NOT NULL"))
.then(() => CreateTable("sampleTable2", "Column1 TEXT NOT NULL, Column2 TEXT NOT NULL"))
.then(() => console.log('Complete'))
.catch(err => console.error(err));
#Aikon Mogwai's answer is good. My suggestion using async/await:
async function createTables() {
let db = new sqlite3.Database(tbpath);
await create_table(db, 'CREATE TABLE IF NOT EXISTS sampleTable(Column1 TEXT NOT NULL, Column2 TEXT NOT NULL)')
await create_table(db, 'CREATE TABLE IF NOT EXISTS sampleTable2(Column1 TEXT NOT NULL, Column2 TEXT NOT NULL)')
db.close();
logger.info("Created database.")
}
async function create_table(db: any, query: any) {
return new Promise(function (resolve, reject) {
db.run(query, function (err: any, rows: any) {
if (err) {
console.log("promise rejected")
return reject(err);
}
resolve(rows);
});
});
}
The new sqlite3.Database(tbpath) doesn't have to be awaited. At least I don't have problems with that.

Callback NodeJS & insert data with mongoose

I know this topic as already asked many times before but I didn't find the right answer to do what I want.
Actually, I try to save two different list of JSON object in MongoDB via Mongoose. To perform both at the same time I use 'async'.
However, when I save it with the command insertMany() I get an error because he calls the callback of async before finishing the insertMany(). Therefore answer[0] is not defined.
What will be the proper way of doing it ?
Here is my code with the async:
const mongoose = require("mongoose");
const async = require("async");
const utils = require("../utils");
const experimentCreate = function(req, res) {
let resData = {};
let experimentList = req.body.experiment;
let datasetList = req.body.datasetList;
async.parallel(
{
dataset: function(callback) {
setTimeout(function() {
answer = utils.createDataset(datasetList);
callback(answer[0], answer[1]);
}, 100);
},
experiment: function(callback) {
setTimeout(function() {
answer = utils.createExp(experimentList);
callback(answer[0], answer[1]);
}, 100);
}
},
function(err, result) {
if (err) {
console.log("Error dataset or metadata creation: " + err);
sendJSONresponse(res, 404, err);
} else {
console.log("Experiment created.");
resData.push(result.dataset);
resData.push(result.experiment);
console.log(resData);
sendJSONresponse(res, 200, resData);
}
}
);
};
Then the two functions called createExp and createDataset are the same in another file. Like this:
const createDataset = function(list) {
let datasetList = [];
for (item of list) {
let temp = {
_id: mongoose.Types.ObjectId(),
name: item.name,
description: item.description,
type: item.type,
};
datasetList.push(temp);
}
Dataset.insertMany(datasetList, (err, ds) => {
if (err) {
console.log("Error dataset creation: " + err);
return [err, null];
} else {
console.log("All dataset created.");
return [null, ds];
}
});
};
There's a few problems with your code. For one, you're not returning anything in your createDataset function. You're returning a value in the callback of insertMany but it doesn't return that value to the caller of createDataset as it's within another scope. To solve this issue, you can wrap your Dataset.insertMany in a promise, and resolve or reject depending on the result of Data.insertMany like this:
const createDataset = function(list) {
let datasetList = [];
for (item of list) {
let temp = {
_id: mongoose.Types.ObjectId(),
name: item.name,
description: item.description,
type: item.type,
};
datasetList.push(temp);
}
return new Promise((resolve, reject) => {
Dataset.insertMany(datasetList, (err, ds) => {
if (err) {
console.log("Error dataset creation: " + err);
reject(err);
} else {
console.log("All dataset created.");
resolve(ds);
}
});
});
};
Now your return object is no longer going to be an array so you won't be able to access both the error and the result via answer[0] and answer[1]. You're going to need to chain a then call after you call createDataset and use callback(null, answer) in the then call (as that means createDataset executed successfully) or use callback(err) if createDataset throws an error like below:
dataset: function(callback) {
setTimeout(function() {
utils.createDataset(datasetList).then(answer => {
callback(null, answer);
}).catch(err => callback(err)); // handle error here);
}, 100);
}
Note: You'll most likely need to alter your createExp code to be structurally similar to what I've produced above if it's also utilizing asynchronous functions.

Find one or create with Mongoose

I have
Page.findById(pageId).then(page => {
const pageId = page.id;
..
});
My problem is that if no page id is given, it should just take the first available page given some conditions, which is done by
Page.findOne({}).then(page => {
const pageId = page.id;
..
});
but if no page is found, it should create a new page and use this, which is done with
Page.create({}).then(page => {
const pageId = page.id;
..
});
But how do I combine all this to as few lines as possible?
I have a lot of logic going on inside
page => { ... }
so I would very much like to do this smart, so I can avoid doing it like this
if (pageId) {
Page.findById(pageId).then(page => {
const pageId = page.id;
..
});
} else {
Page.findOne({}).then(page => {
if (page) {
const pageId = page.id;
..
} else {
Page.create({}).then(page => {
const pageId = page.id;
..
});
}
});
}
I am thinking I maybe could assign a static to the schema with something like
pageSchema.statics.findOneOrCreate = function (condition, doc, callback) {
const self = this;
self.findOne(condition).then(callback).catch((err, result) => {
self.create(doc).then(callback);
});
};
As per the Mongoose docs:
As per previous SO answer
Model.findByIdAndUpdate()
"Finds a matching document, updates it according to the update arg, passing any options, and returns the found document (if any) to the callback."
In the options set upsert to true:
upsert: bool - creates the object if it doesn't exist. defaults to false.
Model.findByIdAndUpdate(id, { $set: { name: 'SOME_VALUE' }}, { upsert: true }, callback)
Related to Yosvel Quintero's answer which didn't work for me:
pageSchema.statics.findOneOrCreate = function findOneOrCreate(condition, callback) {
const self = this
self.findOne(condition, (err, result) => {
return result ? callback(err, result) : self.create(condition, (err, result) => { return callback(err, result) })
})
}
And then use it like:
Page.findOneOrCreate({ key: 'value' }, (err, page) => {
// ... code
console.log(page)
})
Promise async/await version.
Page.static('findOneOrCreate', async function findOneOrCreate(condition, doc) {
const one = await this.findOne(condition);
return one || this.create(doc);
});
Usage
Page.findOneOrCreate({ id: page.id }, page).then(...).catch(...)
Or
async () => {
const yourPage = await Page.findOneOrCreate({ id: page.id }, page);
}
Each Schema can define instance and static methods for its model. Statics are pretty much the same as methods but allow for defining functions that exist directly on your Model
Static method findOneOrCreate:
pageSchema.statics.findOneOrCreate = function findOneOrCreate(condition, doc, callback) {
const self = this;
self.findOne(condition, (err, result) => {
return result
? callback(err, result)
: self.create(doc, (err, result) => {
return callback(err, result);
});
});
};
Now when you have an instance of Page you can call findOneOrCreate:
Page.findOneOrCreate({id: 'somePageId'}, (err, page) => {
console.log(page);
});
One lines solution with async/await:
const page = Page.findOne({}).then(p => p || p.create({})
If you don't want to add a static method to the model, you can try to move some things around and at least not to have all these callback nested levels:
function getPageById (callback) {
Page.findById(pageId).then(page => {
return callback(null, page);
});
}
function getFirstPage(callback) {
Page.findOne({}).then(page => {
if (page) {
return callback(null, page);
}
return callback();
});
}
let retrievePage = getFirstPage;
if (pageId) {
retrievePage = getPageById;
}
retrievePage(function (err, page) {
if (err) {
// #todo: handle the error
}
if (page && page.id) {
pageId = page.id;
} else {
Page.create({}).then(page => {
pageId = page.id;
});
}
});
The solutions posted here ignore that this pattern is most common when there's a unique index on a field or a combination of fields. This solution considers unique index violation errors correctly:
mongoose.plugin((schema) => {
schema.statics.findOrCreate = async function findOrCreate(key, attrs) {
try {
return await this.create({ ...attrs, ...key });
} catch (error) {
const isDuplicateOnThisKey =
error.code === 11000 &&
Object.keys(error.keyPattern).sort().join(',') ===
Object.keys(key).sort().join(',');
if (isDuplicateOnThisKey) {
const doc = await this.findOne(error.keyValue);
doc.set(attrs);
return await doc.save();
}
throw error;
}
};
});
Usage:
await Post.findOrCreate({ slug: 'foobar' }, { title: 'Foo Bar', body });
try this..
var myfunc = function (pageId) {
// check for pageId passed or not
var newId = (typeof pageId == 'undefined') ? {} : {_id:pageId};
Page.findOne(pageId).then(page => {
if (page)
const pageId = page.id;
else { // if record not found, create new
Page.create({}).then(page => {
const pageId = page.id;
});
}
});
}

keystone.js nested promise -> foreach -> list find scope issue

I am writing an service, where I retrieve a list of items from a another service, then iterate over result performing keystone.list operation(s).
I am loosing the return status in the find/exec operation. I have tried promises, async, etc.
If someone could point out the correct way to implement this, I would appreciate it.
general implementation:
exports = module.exports = function (req, res) {
var rtn = {
added: 0,
count: 0
}
service(params)
.then(function(svcResult) {
svcResult.forEach(function(item) {
rtn.count++; // <-- correctly seen in apiresponse
Artifact.model.find()
.where({ artifactId: item.id})
.exec(function(err, result) {
if (result.length == 0) {
result = new Artifact.model({
... populate from item ....
});
result.save();
rtn.added++; // <-- not seen in api response
});
});
res.apiResponse(rtn);
});
}
for starters, exec is an async call, which you are ignoring in your res.apiResponse and thus count is incremented and not added, to make life easy, I am moving the exec call outside and wrapping it with promise:
function pExec(id){
return new Promise(function(resolve, reject){
Artifact.model.find()
.where({ artifactId: id})
.exec(function(err, result){
console.log('result: ', result); // there is a possibility that this is not empty array, which seems to be the only case when you increment added value
err? reject(err): resolve(result);
});
});
}
exports = module.exports = function(req, res){ // I think it is 'exports' not 'exposts'
service(params)
.then(function(svcResult) {
var promises = svcResult.map(function(item){
rtn.count++;
return pExec(item.id).then(function(result){
if (result.length == 0) {
result = new Artifact.model({
//... populate from item ....
});
result.save(); // again this might be an async call whose response you might need before incrementing added...
rtn.added++; // <-- not seen in api response
};
});
});
Promise.all(promises).then(function(){
res.apiResponse(rtn);
});
});
}
Thanks... Here is what I have come up with so far....
function getArtifact(id) {
return new Promise(function (resolve, reject) {
Artifact.model.findOne()
.where({artifactId: id})
.exec(function (err, artifact) {
err ? resolve(null) : resolve(artifact);
});
});
}
function createArtifact(item) {
return new Promise(function (resolve, reject) {
var artifact = new Artifact.model({
// ... populate from item ....
});
artifact.save(function (err, artifact) {
err ? resolve(null) : resolve(artifact);
});
});
}
exports = module.exports = function (req, res) {
var rtn = {
success: false,
count: 0,
section: '',
globalLibrary: {
Added: 0,
Matched: 0
},
messages: [],
};
if (!req.user || !req.user._id) {
rtn.messages.push("Requires Authentication");
return res.apiResponse(rtn);
}
if (!req.params.section) {
rtn.messages.push("Invalid parameters");
return res.apiResponse(rtn);
}
var userId = req.user._id;
var section = req.params.section;
rtn.section = section;
service(section)
.then(function (svcResult) {
if (svcResult.length == 0 || svcResult.items.length == 0) {
rtn.messages.push("Retrieved empty collection");
return;
}
rtn.messages.push("Retrieved collection");
var artifacts = svcResult.items(function (item) {
rtn.count++;
return getArtifact(item.objectid)
.then(function (artifact) {
if (!artifact || artifact.length == 0) {
rtn.messages.push("Global Library Adding: " + item.name['$t']);
rtn.globalLibrary.Added++;
artifact = createArtifact(item);
} else {
rtn.globalLibrary.Matched++;
}
return artifact;
})
});
Promise.all(artifacts)
.then(function () {
rtn.success = true;
res.apiResponse(rtn);
});
});
}

Categories