I have a route where a user can make a post, I want to create the post in one table and update a field in another table. I know that this can be done with back to back inserts, I am just not a hundred percent sure on how to do it. When a user makes a post, I want to create the post as well as update the boolean field hasPosts to true when they submit the post. Here is my route.
router.post('/create', function (req, res, next) {
let token = req.headers['jwt'];
if (token) {
authService.verifyUser(token).then(user => {
if (user) {
models.listings.findOrCreate({
where: { description: req.body.description },
defaults: {
quantity: req.body.quantity,
availability: req.body.availability,
requirements: req.body.requirements,
description: req.body.description,
org_id: user.id,
deleted: false,
}
}).spread(function(created, error) {
if(created) {
console.log(created)
res.status(200).json(created);
} else {
res.status(400).json(error)
}
})
} else {
res.status(500).json(error)
}
});
}
});
How would I go about piggy backing the request to update the users table field hasPosts to true?
Related
I have an array of objects which somewhat looks like this:
[
{
id: '5b29c08b-597c-460c-a3c7-ac8852b7a5dc',
option_text: 'njnj',
answer: false
},
{
id: '8ff5bda6-9335-495c-9c72-15ef258b899b',
option_text: 'jnjn',
answer: true
}
]
Here the answer column is inter-related like if any of the object's answer is set to true the other will come as false from frontend. So I've to update all the row associated with the referenced id.
What problem am facing is that the update query is not running but it is going inside the then block of the code instead of throwing error. Below is my code for the same:
// UPDATE Option
exports.updateOption = (req, res, next) => {
try {
console.log(req.body);
db.Option.update(req.body, {
where: { question_id: req.params.id }
}).then(() => {
console.log('A');
return res.status(200).send(errors.UPDATED_SUCESSFULLY);
}).catch(err => {
console.log('B');
return res.status(204).send(errors.INTERNAL_SERVER);
});
} catch(err) {
console.log('C');
return res.status(204).send(errors.INTERNAL_SERVER);
}
};
Sample Table Data for the same:
What I am thinking is that firstly to answer column false for all the rows associated with the same question_id and then update the particular row which has answer set to true.
But is this a good approach or anyone can suggest me some better solution ?
You should execute all updates in the same transaction (to avoid inconsistencies in DB):
sequelize.transaction(async transaction => {
const options = req.body;
for (const option of options) {
await db.Option.update(option, {
where: { question_id: req.params.id },
transaction
});
}
}).then(...
Hey everyone so I am trying to update a monthly subscription to another monthly subscription in Braintree and prorate the charges. After reading their documentation I am stumped on how to do this effectively. When I go to update the subscription I get the following error message: 'ID has already been taken.'
router.put("/update-subscription", async (req, res, next) => {
try {
console.log("hit route");
if (!res.locals.user) {
throw { status: 403, message: "Not logged in." };
} else {
const { subscriptionId, selectedPlanName } = req.body;
const oldSubscriptionId = subscriptionId;
const selectedPlanId = selectedPlanName.replace(/\s/g, "_");
const userId = res.locals.user.id;
const [[userData]] = await database.query("call getUserByUserId(?)", [
userId
]);
const { braintreeId } = userData;
const { paymentMethods } = await gateway.customer.find("" + braintreeId);
const { token } = paymentMethods.find(p => p.default);
console.log("oldSubscriptionId", oldSubscriptionId);
console.log("selectedPlanId", selectedPlanId);
const subUpdateResponse = await gateway.subscription.update(
oldSubscriptionId,
{
id: selectedPlanId,
paymentMethodToken: token,
options: {
prorateCharges: true
}
}
);
console.log("subUpdateResponse", subUpdateResponse);
if (subUpdateResponse.success) {
res.send("Successfully updated plan");
} else {
throw { message: "An error occurred." };
}
}
} catch (error) {
next(error);
}
});
Heres the Log
subUpdateResponse ErrorResponse {
errors:
ValidationErrorsCollection {
validationErrors: {},
errorCollections: { subscription: [Object] } },
params:
{ id: 'SILVER_MONTHLY',
paymentMethodToken: 'krs2p5',
options: { prorateCharges: 'true' } },
message: 'ID has already been taken.',
success: false }
{ message: 'An error occurred.' }
I understand that the 'SILVER_MONTHLY' ID is already used I mean I am trying to update from one subscription to the other obviously the one I am trying to update to has already been used. Again all I am trying to do is update from the subscription the user is already on to the subscription the user picked to update to. Any help here would be great. Thanks
Full disclosure, I work at Braintree. If you have any further questions, I recommend contacting Support
You are passing the plan ID in the incorrect parameter. The id parameter can be used for a new subscription ID that you'd like to have. To update to a different plan, you'll need to pass a planId parameter, which represents the plan you'd like to update them to.
I also noticed you are passing a paymentMethodToken parameter. This is to be used only if you're updating the payment method token. If that's what you intended, carry on! Otherwise, you do not need to pass this parameter in your update request.
As an example, if you wanted to update the plan and keep the same payment method token, your request may look something like this:
const subUpdateResponse = await gateway.subscription.update(
oldSubscriptionId,
{
planId: selectedPlanId,
options: {
prorateCharges: true
}
}
);
Following is the code:
Accounts.findOrCreate({
where: {
userName: request.payload.userName
},
attributes: { exclude: ['password','sessionToken'] },
defaults: request.payload
}).spread(function (account, created) {
if (created) {
var account = account.get({
plain: true
});
console.log(account); // has the password and sessionToken fields
return reply(account).code(201);
} else {
return reply("user name already exists").code(422);
}
});
I noticed that sequelize first fires a select query in which the password field is not present, then it fires an insert statement in which the password field is present, and that needs to be there.
I would just like the password and sessionToken not be present in the resulting account object. I could of course delete those properties from the object but I am looking for a more straightforward way.
It seems like you need to delete those fields manually. According to the source code, findOrCreate method first fires the findOne function and then it goes with create if instance was not found. The create method does not accept attributes parameter. In such a case all fields will be returned.
Good solution would be to create instance method in the Accounts model in order to return an instance with only the desired attributes.
{
instanceMethods: {
toJson: function() {
let account = {
id: this.get('id'),
userName: this.get('userName')
// and other fields you want to include
};
return account;
}
}
}
Then you could simply use the toJson method when returning raw representation of object:
Accounts.findOrCreate({ where: { userName: 'username' } }).spread((account, created) => {
return account ? account.toJson() : null;
});
As mentioned by piotrbienias you can follow his way otherwise just delete the unwanted elements like this:
Accounts.findOrCreate({
where: {
userName: request.payload.userName
},
defaults: request.payload
}).spread(function (account, created) {
if (created) {
var account = account.get({
plain: true
});
delete account.password;
delete account.sessionToken;
console.log(account); // now you don't have the password and sessionToken fields
return reply(account).code(201);
} else {
return reply("user name already exists").code(422);
}
});
How do you write query resolvers in GraphQL that perform well against a relational database?
Using the example schema from this tutorial, let's say I have a simple database with users and stories. Users can author multiple stories but stories only have one user as their author (for simplicity).
When querying for a user, one might also want to get a list of all stories authored by that user. One possible definition a GraphQL query to handle that (stolen from the above linked tutorial):
const Query = new GraphQLObjectType({
name: 'Query',
fields: () => ({
user: {
type: User,
args: {
id: {
type: new GraphQLNonNull(GraphQLID)
}
},
resolve(parent, {id}, {db}) {
return db.get(`
SELECT * FROM User WHERE id = $id
`, {$id: id});
}
},
})
});
const User = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: {
type: GraphQLID
},
name: {
type: GraphQLString
},
stories: {
type: new GraphQLList(Story),
resolve(parent, args, {db}) {
return db.all(`
SELECT * FROM Story WHERE author = $user
`, {$user: parent.id});
}
}
})
});
This will work as expected; if I query a specific user, I'll be able to get that user's stories as well if needed. However, this does not perform ideally. It requires two trips to the database, when a single query with a JOIN would have sufficed. The problem is amplified if I query multiple users -- every additional user will result in an additional database query. The problem gets worse exponentially the deeper I traverse my object relationships.
Has this problem been solved? Is there a way to write a query resolver that won't result in inefficient SQL queries being generated?
There are two approaches to this kind of problem.
One approach, that is used by Facebook, is to enqueue requests happening in one tick and combine them together before sending. This way instead of doing a request for each user, you can do one request to retrieve information about several users. Dan Schafer wrote a good comment explaining this approach. Facebook released Dataloader, which is an example implementation of this technique.
// Pass this to graphql-js context
const storyLoader = new DataLoader((authorIds) => {
return db.all(
`SELECT * FROM Story WHERE author IN (${authorIds.join(',')})`
).then((rows) => {
// Order rows so they match orde of authorIds
const result = {};
for (const row of rows) {
const existing = result[row.author] || [];
existing.push(row);
result[row.author] = existing;
}
const array = [];
for (const author of authorIds) {
array.push(result[author] || []);
}
return array;
});
});
// Then use dataloader in your type
const User = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: {
type: GraphQLID
},
name: {
type: GraphQLString
},
stories: {
type: new GraphQLList(Story),
resolve(parent, args, {rootValue: {storyLoader}}) {
return storyLoader.load(parent.id);
}
}
})
});
While this doesn't resolve to efficient SQL, it still might be good enough for many use cases and will make stuff run faster. It's also a good approach for non-relational databases that don't allow JOINs.
Another approach is to use the information about requested fields in the resolve function to use JOIN when it is relevant. Resolve context has fieldASTs field which has parsed AST of the currently resolved query part. By looking through the children of that AST (selectionSet), we can predict whether we need a join. A very simplified and clunky example:
const User = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: {
type: GraphQLID
},
name: {
type: GraphQLString
},
stories: {
type: new GraphQLList(Story),
resolve(parent, args, {rootValue: {storyLoader}}) {
// if stories were pre-fetched use that
if (parent.stories) {
return parent.stories;
} else {
// otherwise request them normally
return db.all(`
SELECT * FROM Story WHERE author = $user
`, {$user: parent.id});
}
}
}
})
});
const Query = new GraphQLObjectType({
name: 'Query',
fields: () => ({
user: {
type: User,
args: {
id: {
type: new GraphQLNonNull(GraphQLID)
}
},
resolve(parent, {id}, {rootValue: {db}, fieldASTs}) {
// find names of all child fields
const childFields = fieldASTs[0].selectionSet.selections.map(
(set) => set.name.value
);
if (childFields.includes('stories')) {
// use join to optimize
return db.all(`
SELECT * FROM User INNER JOIN Story ON User.id = Story.author WHERE User.id = $id
`, {$id: id}).then((rows) => {
if (rows.length > 0) {
return {
id: rows[0].author,
name: rows[0].name,
stories: rows
};
} else {
return db.get(`
SELECT * FROM User WHERE id = $id
`, {$id: id}
);
}
});
} else {
return db.get(`
SELECT * FROM User WHERE id = $id
`, {$id: id}
);
}
}
},
})
});
Note that this could have problem with, eg, fragments. However one can handle them too, it's just a matter of inspecting the selection set in more detail.
There is currently a PR in graphql-js repository, which will allow writing more complex logic for query optimization, by providing a 'resolve plan' in the context.
I'm using Sequelize in my Nodejs project and I found a problem that I'm having a hard time to solve.
Basically I have a cron that gets an array of objects from a server than inserts it on my database as a object ( for this case, cartoons ). But if I already have one of the objects, I have to update it.
Basically I have a array of objects and a could use the BulkCreate() method. But as the Cron starts again, it doesn't solve it so I was needing some sort of update with an upsert true flag. And the main issue: I must have a callback that fires just once after all these creates or updates. Does anyone have an idea of how can I do that? Iterate over an array of object.. creating or updating it and then getting a single callback after?
Thanks for the attention
From the docs, you don't need to query where to perform the update once you have the object. Also, the use of promise should simplify callbacks:
Implementation
function upsert(values, condition) {
return Model
.findOne({ where: condition })
.then(function(obj) {
// update
if(obj)
return obj.update(values);
// insert
return Model.create(values);
})
}
Usage
upsert({ first_name: 'Taku' }, { id: 1234 }).then(function(result){
res.status(200).send({success: true});
});
Note
This operation is not atomic.
Creates 2 network calls.
which means it is advisable to re-think the approach and probably just update values in one network call and either:
Look at the value returned (i.e. rows_affected) and decide what to do.
Return success if update operation succeeds. This is because whether the resource exists is not within this service's responsibility.
You can use upsert
It's way easier.
Implementation details:
MySQL - Implemented as a single query INSERT values ON DUPLICATE KEY UPDATE values
PostgreSQL - Implemented as a temporary function with exception handling: INSERT EXCEPTION WHEN unique_constraint UPDATE
SQLite - Implemented as two queries INSERT; UPDATE. This means that the update is executed regardless of whether the row already
existed or not
MSSQL - Implemented as a single query using MERGE and WHEN (NOT) MATCHED THEN Note that SQLite returns undefined for created, no
matter if the row was created or updated. This is because SQLite
always runs INSERT OR IGNORE + UPDATE, in a single query, so there
is no way to know whether the row was inserted or not.
Update 07/2019 now with async/await
async function updateOrCreate (model, where, newItem) {
// First try to find the record
const foundItem = await model.findOne({where});
if (!foundItem) {
// Item not found, create a new one
const item = await model.create(newItem)
return {item, created: true};
}
// Found an item, update it
const item = await model.update(newItem, {where});
return {item, created: false};
}
I liked the idea of Ataik, but made it a little shorter:
function updateOrCreate (model, where, newItem) {
// First try to find the record
return model
.findOne({where: where})
.then(function (foundItem) {
if (!foundItem) {
// Item not found, create a new one
return model
.create(newItem)
.then(function (item) { return {item: item, created: true}; })
}
// Found an item, update it
return model
.update(newItem, {where: where})
.then(function (item) { return {item: item, created: false} }) ;
}
}
Usage:
updateOrCreate(models.NewsItem, {slug: 'sometitle1'}, {title: 'Hello World'})
.then(function(result) {
result.item; // the model
result.created; // bool, if a new item was created.
});
Optional: add error handling here, but I strongly recommend to chain all promises of one request and have one error handler at the end.
updateOrCreate(models.NewsItem, {slug: 'sometitle1'}, {title: 'Hello World'})
.then(..)
.catch(function(err){});
This might be an old question, but this is what I did:
var updateOrCreate = function (model, where, newItem, onCreate, onUpdate, onError) {
// First try to find the record
model.findOne({where: where}).then(function (foundItem) {
if (!foundItem) {
// Item not found, create a new one
model.create(newItem)
.then(onCreate)
.catch(onError);
} else {
// Found an item, update it
model.update(newItem, {where: where})
.then(onUpdate)
.catch(onError);
;
}
}).catch(onError);
}
updateOrCreate(
models.NewsItem, {title: 'sometitle1'}, {title: 'sometitle'},
function () {
console.log('created');
},
function () {
console.log('updated');
},
console.log);
User.upsert({ a: 'a', b: 'b', username: 'john' })
It will try to find record by hash in 1st param to update it, if it will not find it - then new record will be created
Here is example of usage in sequelize tests
it('works with upsert on id', function() {
return this.User.upsert({ id: 42, username: 'john' }).then(created => {
if (dialect === 'sqlite') {
expect(created).to.be.undefined;
} else {
expect(created).to.be.ok;
}
this.clock.tick(1000);
return this.User.upsert({ id: 42, username: 'doe' });
}).then(created => {
if (dialect === 'sqlite') {
expect(created).to.be.undefined;
} else {
expect(created).not.to.be.ok;
}
return this.User.findByPk(42);
}).then(user => {
expect(user.createdAt).to.be.ok;
expect(user.username).to.equal('doe');
expect(user.updatedAt).to.be.afterTime(user.createdAt);
});
});
Sound likes you want to wrap your Sequelize calls inside of an async.each.
This can be done with the custom event emitter.
Assuming your data is in a variable called data.
new Sequelize.Utils.CustomEventEmitter(function(emitter) {
if(data.id){
Model.update(data, {id: data.id })
.success(function(){
emitter.emit('success', data.id );
}).error(function(error){
emitter.emit('error', error );
});
} else {
Model.build(data).save().success(function(d){
emitter.emit('success', d.id );
}).error(function(error){
emitter.emit('error', error );
});
}
}).success(function(data_id){
// Your callback stuff here
}).error(function(error){
// error stuff here
}).run(); // kick off the queries
you can use findOrCreate and then update methods in sequelize. here is a sample with async.js
async.auto({
getInstance : function(cb) {
Model.findOrCreate({
attribute : value,
...
}).complete(function(err, result) {
if (err) {
cb(null, false);
} else {
cb(null, result);
}
});
},
updateInstance : ['getInstance', function(cb, result) {
if (!result || !result.getInstance) {
cb(null, false);
} else {
result.getInstance.updateAttributes({
attribute : value,
...
}, ['attribute', ...]).complete(function(err, result) {
if (err) {
cb(null, false);
} else {
cb(null, result);
}
});
}
}]
}, function(err, allResults) {
if (err || !allResults || !allResults.updateInstance) {
// job not done
} else {
// job done
});
});
Here is a simple example that either updates deviceID -> pushToken mapping or creates it:
var Promise = require('promise');
var PushToken = require("../models").PushToken;
var createOrUpdatePushToken = function (deviceID, pushToken) {
return new Promise(function (fulfill, reject) {
PushToken
.findOrCreate({
where: {
deviceID: deviceID
}, defaults: {
pushToken: pushToken
}
})
.spread(function (foundOrCreatedPushToken, created) {
if (created) {
fulfill(foundOrCreatedPushToken);
} else {
foundOrCreatedPushToken
.update({
pushToken: pushToken
})
.then(function (updatedPushToken) {
fulfill(updatedPushToken);
})
.catch(function (err) {
reject(err);
});
}
});
});
};
2022 update:
You can use the upsert function:
https://sequelize.org/api/v6/class/src/model.js~model#static-method-upsert
Insert or update a single row. An update will be executed if a row which matches the supplied values on either the primary key or a unique key is found. Note that the unique index must be defined in your sequelize model and not just in the table. Otherwise you may experience a unique constraint violation, because sequelize fails to identify the row that should be updated.
Implementation details:
MySQL - Implemented with ON DUPLICATE KEY UPDATE`
PostgreSQL - Implemented with ON CONFLICT DO UPDATE. If update data contains PK field, then PK is selected as the default conflict key.
Otherwise first unique constraint/index will be selected, which can satisfy conflict key requirements.
SQLite - Implemented with ON CONFLICT DO UPDATE
MSSQL - Implemented as a single query using MERGE and WHEN (NOT) MATCHED THEN
Note that Postgres/SQLite returns null for created, no matter if the row was created or updated