MongoDB Duplicate Entry - mongodb

I have the following schema
var customerSchema = new Schema({
customerID: String,
phoneNumber: String
})
customerSchema.path('customerID').validate(function (customerID, next) {
Customer.findOne({customerID: customerID}, function (err, user) {
if (err) {
return next(false);
}
if (!user) {
return next(true); //Valid
} else {
return next(false);
}
});
}, 'customerID Already Exists');
This works perfectly when I was trying to add the same customerID. It will not let me. But previously I tried to press the ADD button at the same time on different computer. Somehow the same customerID gets added.
How to prevent such edge cases from happening? I am using MongoLab. Does the latency present a big problem?

It's very possible to have this type of behavior:
Press add button on computer1
Press add button on computer2
Validation occurs for the first add and the customer does not exist
Validation occurs for the second add BEFORE the first insert is done and the customer does not yet exist
Both adds succeed
You can put a unique constraint on the field at the database level to prevent this - https://docs.mongodb.org/v3.0/tutorial/create-a-unique-index/
Depending on your needs, you can also have an "upsert" that updates or inserts a new record. In your case, if you're concerned about two users creating a new user with the same ID, just put an index on it at the database.

Related

ExtJS - How can I rollback selection to old selected record in Grid?

It is using grid of ExtJS.
I would like to do some validation before record in grid change to another record.
Below is my sample code:
xtype: 'grid',
bind: {
store:'{xxStore}'
},
columns: [
{
text: 'Name',
dataIndex: 'name',
}
],
listeners:{
beforeselect: 'onBeforeSelect',
select: 'onSelect'
}
In Controller, sample code is as below:
onBeforeSelect: function(grid) {
// whether can select or not
if (this.getSettingType() === 'global') {
return false;
}
},
onSelect: async function(grid, itemRecord) {
if (!(await checkUserAction())) {
// TODO: how to rollback to old selected record here or onBeforeSelect?
}
},
checkUserAction: function() {
return new Promise((resolve) => {
let me = this;
Ext.Msg.show({
buttons: Ext.Msg.YESNO,
title: 'Confirm Discard Changes',
msg: 'Do you want to discard changes?',
callback: function(button) {
if (button == 'yes') {
resolve(true);
} else {
resolve(false);
}
}
});
});
}
How to perform it?
Supplement the selection flow:
On the model (records) you do a reject. On the store you do a rejectChanges this will call reject on each record.
EDIT: - each record (instance of Ext.data.Model or descendant) is either a new record (phantom = true) an existing record, an existing record that has been modified (dirty = true) or an existing record that has been erased (erased = true). Any existing record may have fields that have been changed since it was originally loaded or last committed.
When a record is inserted into the store if the idPropery is set with a value then it is considered an existing record if not then it is a new record (phantom = true) that has not yet been committed and therefore a "phantom". Same with record that you execute the erase() method. It is to be deleted. The record is still in the store just set to erased=true. When you execute the sync() method on the store phantom records are saved in the database, modified records has is changes saved and erased records are removed from the database. How all these changes are done is based on the proxy of the model.... after the sync is called the phantom records are no longer phantoms but existing records. erased records are removed from the store and modified records are no longer considered "dirty" or modified.
If you do a rejectChanges on the store prior to the sync() then the store will go back to how it was after the last sync() or load(). Phantom records will be removed, erased records will to back to not being erased and modified records will have all changes set back to how they were prior to the change.
So in your case, set the idProperty (in the model) to the primary key of the record and make sure it has a value when you insert the records into the store. Then when you do a rejectChanges on the store it will go back to the way it was prior to the changes. You can run commitChanges() on the store that will make set all records to unmodified, not phantom and not erased.
FYI: If you don't pass an id property sencha will assign a unique key to the record but still make it a phantom (phantom = true)

MongoDB pre save set field based on lookup logic

People register for an event. There are two collections in the database. One for new registrations coming in and one for the registrations of previous years. Both contain an email field as unique identifier.
I would like to know if its possible to check if a newly registered person has registered before in previous years. If so add a field, for example: returningCustomer: true. Otherwise add returningCustomer: false
I am using Mongoose and have a User model for new registrations. I don't have a model (yet) for previously registered users. Would that be neccesary? If it is possible to check if a person has registered before and a field can be added before saving, it might be handy to save the user to the returning customers collection immediatly as well.
I know it is possible to access the current document and collection using a pre save hook, but how about doing a lookup in another collection, write a bit of logic and add a field to the current document pre save?
userSchema.pre('save', function (doc, next) {
const exists = otherCollection.find({ email: doc.email });
exists ? doc.returningCustomer = true : doc.returningCustomer = false;
next();
});
You should have a model for the collection you want to lookup.
Then you can query the other collection before saving the current collection.
CurrentModel.pre('save', async function (next) {
const doc = await OtherModel.find({ field: this.field });
doc.length ? this.returningCustomer = false : this.returningCustomer = true;
next();
});

create personal collections for logged in users

import {favRestaurants} from '/lib/collections';
import {Meteor} from 'meteor/meteor';
import {check} from 'meteor/check';
export default function () {
Meteor.methods({
'favRestaurants.create' (id, name, rating, priceLevel, type) {
check(id, String);
check(name, String);
check(rating, Number);
check(priceLevel, Number);
check(type, String);
const createdAt = new Date();
const restaurant = {id, name, rating, priceLevel, type, createdAt};
if(check(Meteor.user()) == null){
console.log('onlye logged in users can data');
}else{
FavRestaurants.insert(restaurant);
}
}
});
}
This is my insert method for adding data to the restaurants collections. When i console log the 'check(Meteor.user())' in the console i get null as output. By that logic you shouldn't be able to add data to the collection, although this is still possible.
I would also like to make the FavResaurants collection personal for each user. Iv'e tried to check if there is a user and then adding a collection in the main.js file on the client side.
Meteor.loggingIn(() => {
console.log('check for user method');
var restId = 0;
if(Meteor.user() != null){
console.log('created new collection for the user');
const FavRestaurants = new Mongo.Collection('favRestaurants' + restId);
}
restId++;
});
I dont get any output to console using this method, which i found in the meteor docs. Is the code in the wrong place? Any help is much appriciated.
According to the docs the Accounts.ui.config is the method i should use. But I'm not sure in code i should put it. So far the placement of this method has resulted in my application crashing.
Answering your first question, to allow only logged-in clients to access a method, you should use something like:
if (!Meteor.userId()) {
throw new Meteor.Error('403', 'Forbidden');
}
Now, I see you want a collection to store favorite restaurants for each user in client side. But as I see it, there'd be only one logged in user per client, so you don't need a separate collection for each user (as the collection is in each client), you can just refer the user with it's id, and then fetch a user's favorite restaurants by a query like:
FavRestaurants.find({user: Meteor.userId()});
Moreover, as the docs suggest, Meteor.loggingIn is a method which tells you if some user is in the process of logging in. What you are doing is over-riding it, which doesn't make sense.
You should do something like:
if (Meteor.loggingIn()) {
// Do your stuff
}
Hope it gives you more clarity.
Creating a collection per user is a bad approach.
Define your favRestaurants collection once and add a owner field in the restaurant document before insert.
Create a publish method to publish to client side the userid favrestaurant only.
One more thing, check your userid first in your Meteor method, it will avoid unnecessary proces.
Regs

bookshelf js save() command not updating rows in postgresql db

JS beginner trying to get a PostgreSQL DB talking to express.js through bookshelf.js.
github: https://github.com/duskyshelf/bookers-academy/blob/master/booker.js
var knex = require('knex')({
client: 'pg',
connection: "postgres://localhost/bookers"
});
var bookshelf = require('bookshelf')(knex);
var User = bookshelf.Model.extend({
tableName: 'users'
});
var bob = new User({id: 2});
bob.save()
bookshelf.js seems unable to add any content to the db.
Current error message is: "Unhandled rejection CustomError: No Rows Updated'
When you create your model providing your own id, like in
var bob = new User({id: 2});
Bookshelf assumes it is an update operation, not an insertion. It sets the internal isNew attribute to false, and when save() is invoked, instead of INSERT INTO user(id, ...) VALUES (2, ...);, it executes UPDATE user ... WHERE id = 2;.
If there is no user with id = 2 the update will almost silently DO NOTHING.
To force an insert you must change the save() to:
bob.save(null, {method: 'insert'});
Bookshelf save() documentation describes this behavior.
Did you create a User table using knex? One potential problem I can think of is that you do not have any logic that actually creates the table for your Postgres DB. Here is some sample code that will create a table in your database if it doesn't yet exist.
bookshelf.knex.schema.hasTable('User').then(function(exists) {
if(!exists) {
bookshelf.knex.schema.createTable('User'), function(user) {
user.increments('id').primary();
user.timestamps();
}).then(function(table){
console.log('Created Table:', table);
});
}
});
new User({id: 2}).
save().
then((model) => {
res.json({ success: true });
});
No no! new User returns a PROMISE! You can't use it like that.
Try
new User().save()

mongoosejs upsert with on save hook

I'm trying to add data to my mongo database with mongoose, but there is a high probability that most of the data is already in the database, only a small number of fields need to be updated. Creation time for the record and last time updated need to be saved.
My first attempt at solving this problem included using the Model.save function, given that my model is called server, and data is an object coming from an external http service, which specifies the unique _id in data.
var instance = new Server(data);
instance.save(function(err){
if(err)
console.log(err);
});
also my pre-save hook:
ServerSchema.pre('save', function(next) {
this.lastseen = Date.now();
if (!this.isNew)
return next() //if the entry isn't new, lets not change the date registred
this.registered = Date.now();
next() //Don't forget this!
})
The problem here is that on duplicate _id the save chokes, with error E11000 duplicate key error index...
This now makes sense as save only does an update when the document instance is not created using the new operator.
So in my next attempt, I added code to attempt to lookup the document, then used underscore.js's _.extend to merge the new document with the one found in the database, then saved that to the database. The problem with this approach is that it require an extra call to the database for each chunk of data being processed.
My third attempt uses the Model.findByIdAndUpdate with {upsert:true} this works, in terms of stroring the data in the database, but schema defaults and my pre-save hook isn't triggered.
The fourth attempt uses code suggested by #aheckmann in this gist: https://gist.github.com/2764948
var server = new Server();
server.init(ping);
server.save(function(err){
if(err) {
console.log("DB Error: ",err);
return res.send('DB Error')
}
//if server approved, tell the inworld server to sync textures
if(server.approved)
res.send('success')
else
res.send('skip')
user.servers.addToSet(ping._id); //add the server to the user's list
user.save(function(err, usr){
if(err)
console.log("DB Error: ", err);
})
})
Here again, the pre-save hook isn't triggered. Am I to understand that the only to upsert with hooks is to attempt to find the document first with a findById ?
Q:
Is there a way to "upsert" Insert or Update based on the primary unique key without making more than one database call per chunk of data? Is there a method, or obvious fact that I am overlooking?
I don't think that you can do it with less then two calls to DB, unless you'll drop mongoose part and use mongo driver directly. But you can create a static method to do all the job for you:
ServerSchema.statics.findOrCreate(function(doc, next) {
this.findById(doc._id, function(err, res) {
res || (res = new this);
_.extend(res, doc); // add new data to the document
next(err, res); // if (err != null) then something went wrong
});
});
findByIdAndUpdate not triggers presave hook because it calls mongo driver directly.